puzhibing
6 小时以前 e1c8c9d51f1c6e42c9868743f16fa8e52bb419cd
Merge branch '2.3' of http://120.76.84.145:10101/gitblit/r/java/mx_charging_pile into 2.3
1个文件已添加
13个文件已修改
203 ■■■■■ 已修改文件
ruoyi-api/ruoyi-api-account/src/main/java/com/ruoyi/account/api/model/TChargingCard.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/mapper/TChargingCardMapper.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/service/impl/TChargingCardServiceImpl.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-account/src/main/resources/mapper/account/TChargingCardMapper.xml 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TCardChargingOrderServiceImpl.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TChargingOrderServiceImpl.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TVinChargingOrderServiceImpl.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/emind/service/EmindEnergyChainDetailService.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/energyreport/vo/StorageDailyReportRow.java 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/service/impl/TStationInfoServiceImpl.java 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/udan/model/PeripheralDeviceState.java 79 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/udan/model/UdanEmsDeviceState.java 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/udan/service/UdanEmsMessageProcessor.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/util/HuWeiUtils.java 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-api/ruoyi-api-account/src/main/java/com/ruoyi/account/api/model/TChargingCard.java
@@ -31,6 +31,12 @@
    @ApiModelProperty(value = "卡号")
    private String cardNo;
    /** 完整卡号(固定16位,不足前面补0;仅充电桩刷卡查询用,不在前端展示,不对外返回) */
    @JsonIgnore
    @TableField("full_card_no")
    @ApiModelProperty(value = "完整卡号(固定16位,不足前面补0,仅充电查询用,不对外返回)")
    private String fullCardNo;
    /** 卡密码(BCrypt 密文);为空表示未配置密码,刷卡启动时不校验密码(V0.2 定稿) */
    @JsonIgnore
    @ApiModelProperty(value = "卡密码(BCrypt 密文,仅内部存储与校验,不对外返回;空表示未配置,刷卡时不校验密码)")
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/mapper/TChargingCardMapper.java
@@ -20,6 +20,8 @@
    int countByCardNo(@Param("cardNo") String cardNo, @Param("excludeId") Long excludeId);
    int countByFullCardNo(@Param("fullCardNo") String fullCardNo, @Param("excludeId") Long excludeId);
    int adjustBalance(@Param("ids") List<Long> ids,
                      @Param("amount") BigDecimal amount,
                      @Param("type") Integer type,
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/service/impl/TChargingCardServiceImpl.java
@@ -70,6 +70,8 @@
    private static final long SYSTEM_OPERATOR = 0L;
    private static final BigDecimal MAX_AMOUNT = new BigDecimal("9999999999.99");
    private static final int PASSWORD_MAX_LENGTH = 64;
    /** 完整卡号固定长度:16位,不足前面补0(仅充电桩刷卡查询用,不在前端展示) */
    private static final int CARD_NO_FULL_LENGTH = 16;
    /** 公司账户模式:2=分配模式(可设置初始分配额度并扣减公司可用余额) */
    private static final int ACCOUNT_MODE_ALLOCATION = 2;
    /** 公司账户模式:1=共享模式(可用余额取公司可用余额) */
@@ -135,6 +137,7 @@
        try {
            TChargingCard card = new TChargingCard();
            card.setCardNo(cardNo);
            card.setFullCardNo(buildFullCardNo(cardNo));
            card.setBalance(quota.signum() > 0 ? quota : BigDecimal.ZERO);
            card.setStatus(dto.getStatus() == null ? ENABLED : dto.getStatus());
            card.setPassword(encodePasswordIfPresent(dto.getPassword()));
@@ -188,6 +191,7 @@
            TChargingCard card = new TChargingCard();
            card.setId(dto.getId());
            card.setCardNo(dto.getCardNo().trim());
            card.setFullCardNo(buildFullCardNo(card.getCardNo()));
            card.setStatus(dto.getStatus() == null ? ENABLED : dto.getStatus());
            card.setUpdateTime(LocalDateTime.now());
            card.setUpdateBy(SecurityUtils.getUserId());
@@ -435,6 +439,7 @@
            throw new ServiceException("导入数据不能为空");
        }
        Set<String> importCardNos = new HashSet<>();
        Set<String> importFullCardNos = new HashSet<>();
        int row = 1;
        for (ChargingCardExcelDTO item : cards) {
            row++;
@@ -451,6 +456,13 @@
            if (existsCardNo(cardNo, null)) {
                throw new ServiceException("第" + row + "行卡号已存在");
            }
            String fullCardNo = buildFullCardNo(cardNo);
            if (!importFullCardNos.add(fullCardNo)) {
                throw new ServiceException("第" + row + "行卡号补零后与其他卡号重复");
            }
            if (existsFullCardNo(fullCardNo, null)) {
                throw new ServiceException("第" + row + "行卡号已存在");
            }
            if (item.getBalance() != null) {
                validateAmount(item.getBalance(), true, "第" + row + "行卡余额");
            }
@@ -464,6 +476,7 @@
        for (ChargingCardExcelDTO item : cards) {
            TChargingCard card = new TChargingCard();
            card.setCardNo(item.getCardNo().trim());
            card.setFullCardNo(buildFullCardNo(card.getCardNo()));
            if (lockedCompanyId != null) {
                card.setCompanyId(lockedCompanyId);
            }
@@ -483,8 +496,10 @@
        if (dto == null || StringUtils.isEmpty(dto.getCardNo())) {
            throw new ServiceException("卡号不能为空");
        }
        // 按完整卡号(16位补零)查询,与充电桩上报的完整卡号口径一致
        String fullCardNo = buildFullCardNo(dto.getCardNo());
        TChargingCard card = getOne(new LambdaQueryWrapper<TChargingCard>()
                .eq(TChargingCard::getCardNo, dto.getCardNo().trim()));
                .eq(TChargingCard::getFullCardNo, fullCardNo));
        if (card == null) {
            throw new ServiceException("卡片不存在或已删除");
        }
@@ -1075,6 +1090,7 @@
        TChargingCard update = new TChargingCard();
        update.setId(card.getId());
        update.setCardNo(card.getCardNo());
        update.setFullCardNo(card.getFullCardNo());
        update.setStatus(card.getStatus());
        update.setPassword(encodedPassword);
        update.setUpdateTime(card.getUpdateTime());
@@ -1134,6 +1150,11 @@
        }
    }
    /** 完整卡号规范化:固定16位,不足前面补0(trim 后左补0) */
    private String buildFullCardNo(String cardNo) {
        return StringUtils.padl(cardNo == null ? "" : cardNo.trim(), CARD_NO_FULL_LENGTH, '0');
    }
    private void validateCard(ChargingCardSaveDTO dto, boolean update) {
        if (dto == null || StringUtils.isEmpty(dto.getCardNo())) {
            throw new ServiceException("卡号不能为空");
@@ -1157,6 +1178,9 @@
            throw new ServiceException("卡片状态参数错误");
        }
        if (existsCardNo(cardNo, dto.getId())) {
            throw new ServiceException("卡号已存在");
        }
        if (existsFullCardNo(buildFullCardNo(cardNo), dto.getId())) {
            throw new ServiceException("卡号已存在");
        }
    }
@@ -1194,6 +1218,10 @@
        return baseMapper.countByCardNo(cardNo, excludeId) > 0;
    }
    private boolean existsFullCardNo(String fullCardNo, Long excludeId) {
        return baseMapper.countByFullCardNo(fullCardNo, excludeId) > 0;
    }
    private void validateDeductRequest(ChargingCardDeductDTO dto) {
        if (dto == null) {
            throw new ServiceException("扣款参数不能为空");
ruoyi-service/ruoyi-account/src/main/resources/mapper/account/TChargingCardMapper.xml
@@ -65,6 +65,15 @@
        </if>
    </select>
    <select id="countByFullCardNo" resultType="int">
        SELECT COUNT(1)
        FROM t_charging_card
        WHERE full_card_no = #{fullCardNo}
        <if test="excludeId != null">
            AND id != #{excludeId}
        </if>
    </select>
    <!-- 人工调账:仍保持"增加不超过上限、扣减不低过 0"的运营约束,并叠加充电锁定条件兜底(V0.2 定稿) -->
    <update id="adjustBalance">
        UPDATE t_charging_card
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TCardChargingOrderServiceImpl.java
@@ -740,7 +740,7 @@
     * 1) 企业状态校验(未归属/不存在/停用 -> 失败码2),账户模式取企业配置(默认共享);
     * 2) 企业充电授权校验:站点范围(超范围 -> 失败码2)、可用时段(不在时段 -> 失败码2),未配置授权按放行处理;
     * 3) 可用余额校验(共享=企业可用余额,分配=卡可用余额,<=0 -> 失败码3);
     * 4) 下发金额 = min(授权下发额, 可用余额),锁定额度与下发金额一致;授权截止时间用于监控提前停充。
     * 4) 下发金额:共享模式=min(授权下发额, 可用余额),分配模式=卡账户余额;锁定额度与下发金额一致;授权截止时间用于监控提前停充。
     */
    private ChargeAccountSupport.ChargeContext resolveCardChargeContext(Integer siteId, Integer strategyId,
                                                                       TChargingCard card) {
@@ -796,12 +796,17 @@
            return ChargeAccountSupport.ChargeContext.fail(3, "账户可用余额不足");
        }
        // 4. 下发金额 = min(授权下发额, 可用余额),锁定金额与下发金额一致
        BigDecimal dispatchAmount = authorization == null || authorization.getDispatchAmount() == null
        // 4. 下发金额:共享模式=min(授权下发额, 可用余额),分配模式=卡账户余额;锁定金额与下发金额一致
        BigDecimal dispatchAmount;
        if (Integer.valueOf(ChargeAccountSupport.ACCOUNT_MODE_ALLOCATED).equals(accountMode)) {
            dispatchAmount = usable;
        } else {
            dispatchAmount = authorization == null || authorization.getDispatchAmount() == null
                ? ChargeAccountSupport.DEFAULT_DISPATCH_AMOUNT : authorization.getDispatchAmount();
        if (dispatchAmount.compareTo(usable) > 0) {
            dispatchAmount = usable;
        }
        }
        if (dispatchAmount.compareTo(BigDecimal.ZERO) <= 0) {
            return ChargeAccountSupport.ChargeContext.fail(3, "账户可用余额不足");
        }
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TChargingOrderServiceImpl.java
@@ -808,10 +808,10 @@
     * 解析白名单企业账户充电上下文(白名单用户选择余额支付时调用):
     * 1) 校验归属公司存在且启用,未归属/停用直接拒绝;
     * 2) 校验企业充电授权(站点范围、可用时段/电价阶段)并计算授权截止时间,未配置授权按放行处理(对齐 VIN 口径);
     * 3) 下发额度 = min(用户输入充值金额, 账户可用额度, 企业设定的充值金额):
     * 3) 下发额度:
     *    - 账户可用额度:共享模式=公司可用余额,分配模式=白名单账户余额;
     *    - 企业设定充值金额=授权配置下发金额,未配置默认 200 元;
     *    - 用户输入金额为空或非正数时按 min(企业设定充值金额, 账户可用额度) 下发。
     *    - 共享模式=min(用户输入充值金额, 账户可用额度, 企业设定充值金额),企业设定充值金额=授权配置下发金额,未配置默认 200 元;
     *    - 分配模式=min(用户输入充值金额, 白名单账户余额),用户输入为空或非正数时直接下发白名单账户余额(不再受企业设定充值金额限制)。
     *
     * @param whitelist       白名单命中结果(含归属公司与账户余额快照)
     * @param userInputAmount 用户输入的充值金额
@@ -871,14 +871,19 @@
        if (usable.compareTo(BigDecimal.ZERO) <= 0) {
            return ChargeAccountSupport.ChargeContext.fail(3, "企业额度不足,请选择其他支付方式");
        }
        BigDecimal dispatch = money(userInputAmount);
        if (accountMode == ChargeAccountSupport.ACCOUNT_MODE_ALLOCATED) {
            // 分配模式:直接下发白名单账户余额(用户输入正数时以用户输入为上限),不再受公司充电下发金额限制
            dispatch = dispatch.compareTo(BigDecimal.ZERO) <= 0 ? usable : dispatch.min(usable);
        } else {
        BigDecimal enterpriseLimit = authorization == null ? null : authorization.getDispatchAmount();
        if (enterpriseLimit == null || enterpriseLimit.compareTo(BigDecimal.ZERO) <= 0) {
            enterpriseLimit = ChargeAccountSupport.DEFAULT_DISPATCH_AMOUNT;
        }
        BigDecimal dispatch = money(userInputAmount);
        dispatch = dispatch.compareTo(BigDecimal.ZERO) <= 0
                ? enterpriseLimit.min(usable)
                : dispatch.min(enterpriseLimit).min(usable);
        }
        return ChargeAccountSupport.ChargeContext.ok(accountMode, dispatch.setScale(2, RoundingMode.HALF_UP), maxEndTime);
    }
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TVinChargingOrderServiceImpl.java
@@ -1107,13 +1107,19 @@
        if (usable.compareTo(ZERO_AMOUNT) <= 0) {
            return VinChargeContext.fail(3, "账户可用余额为0 accountMode=" + accountMode + " companyId=" + companyId);
        }
        BigDecimal dispatch = authorization == null ? null : authorization.getDispatchAmount();
        BigDecimal dispatch;
        if (accountMode == ACCOUNT_MODE_ALLOCATED) {
            // 分配模式:直接下发 VIN 账户余额,不再受公司充电下发金额限制
            dispatch = usable;
        } else {
            dispatch = authorization == null ? null : authorization.getDispatchAmount();
        if (dispatch == null || dispatch.compareTo(ZERO_AMOUNT) <= 0) {
            dispatch = VD_DEFAULT_DISPATCH_AMOUNT;
        }
        if (dispatch.compareTo(usable) > 0) {
            dispatch = usable;
        }
        }
        return VinChargeContext.ok(accountMode, dispatch, maxEndTime);
    }
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/emind/service/EmindEnergyChainDetailService.java
@@ -102,6 +102,16 @@
            detail.setStationType("BMS");
            detailList.add(detail);
        }
        if(Objects.nonNull(udanEmsDeviceState.getPeripheralDeviceState())) {
            EmindEnergyChainDetail detail = new EmindEnergyChainDetail();
            detail.setPlatformName("欧力特");
            detail.setDataSource(2);
            detail.setStationCode(udanEmsDeviceState.getEmsSystemId());
            detail.setDetail(JSON.toJSONString(udanEmsDeviceState.getPeripheralDeviceState()));
            detail.setStationId(udanEmsDeviceState.getTmsState().getDeviceId());
            detail.setStationType("外设");
            detailList.add(detail);
        }
        detailList.forEach(d -> {
            mapper.delete(new QueryWrapper<EmindEnergyChainDetail>().eq("station_code", d.getStationCode()).eq("station_type", d.getStationType()));
            add(d);
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/energyreport/vo/StorageDailyReportRow.java
@@ -12,8 +12,8 @@
    @ExcelProperty("电站ID") private String stationId;
    @ExcelProperty("电站名称") private String stationName;
    @ExcelProperty("日期") private LocalDate statDate;
    @ExcelProperty("当日电价") private BigDecimal price;
    @ExcelProperty("电价名称") private String priceName;
/*    @ExcelProperty("当日电价") private BigDecimal price;
    @ExcelProperty("电价名称") private String priceName;*/
    @ExcelProperty("总收益(元)") private BigDecimal income;
    @ExcelProperty("总充电量(kWh)") private BigDecimal chargedEnergy;
    @ExcelProperty("尖时段充电量(kWh)") private BigDecimal chargedEnergySharp;
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/service/impl/TStationInfoServiceImpl.java
@@ -457,6 +457,7 @@
            info.setLoanStatus(gridTime != null && gridTime.isBefore(LocalDateTime.now()) ? 3 : 1);
            info.setGridStatus(info.getLoanStatus() == 3 ? "1" : "0");
            info.setDataSource(2);
            info.setDeviceCount(1);
            info.setSyncTime(LocalDateTime.now());
            info.setCreateTime(LocalDateTime.now());
            info.setUpdateTime(LocalDateTime.now());
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/udan/model/PeripheralDeviceState.java
New file
@@ -0,0 +1,79 @@
package com.ruoyi.other.udan.model;
import lombok.Data;
import java.io.Serializable;
import java.util.Objects;
/**
 * 外设
 *
 * @author Lux
 * @version 1.0.0
 * @since 2026/10/12 21:24
 */
@Data
public class PeripheralDeviceState implements Serializable {
    /**
     * 设备id
     */
    private String deviceId;
    /**
     * 原始di_state值
     */
    private Integer diState;
    /**
     * 急停状态 bit1
     */
    private Boolean emergencyStop;
    /**
     * 消防状态 bit2
     */
    private Boolean fireStatus;
    /**
     * 水浸状态 bit5
     */
    private Boolean waterLeak;
    /**
     * 消防喷洒 bit7
     */
    private Boolean fireSpray;
    /**
     * bit解析
     *
     * bit1 急停
     * bit2 消防
     * bit5 水浸
     * bit7 消防喷洒
     */
    public void convert() {
        if(Objects.nonNull(this.getDiState())) {
            this.setEmergencyStop(getBit(this.getDiState(),1));
            this.setFireStatus(getBit(this.getDiState(),2));
            this.setWaterLeak(getBit(this.getDiState(),5));
            this.setFireSpray(getBit(this.getDiState(),7));
        }
    }
    /**
     * 获取bit状态
     *
     * @param value 原始值
     * @param bit   bit编号,从1开始
     */
    public boolean getBit(int value, int bit) {
        return ((value >> (bit - 1)) & 1) == 1;
    }
}
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/udan/model/UdanEmsDeviceState.java
@@ -26,6 +26,7 @@
    private PcsState pcsState;
    private ElectricMeterState electricMeterState;
    private BatteryStackState batteryStackState;
    private PeripheralDeviceState peripheralDeviceState;
    public List<EmindIncomeHistoryDto> convert(Long stationId) {
        List<EmindIncomeHistoryDto> dailyList = new ArrayList<>();
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/udan/service/UdanEmsMessageProcessor.java
@@ -8,13 +8,7 @@
import com.ruoyi.other.emind.mapper.EmindStationMapper;
import com.ruoyi.other.emind.mapper.EmindStorageDashboardMapper;
import com.ruoyi.other.emind.service.EmindEnergyChainDetailService;
import com.ruoyi.other.udan.model.EmsState;
import com.ruoyi.other.udan.model.BatteryStackState;
import com.ruoyi.other.udan.model.ElectricMeterState;
import com.ruoyi.other.udan.model.PcsState;
import com.ruoyi.other.udan.model.PowerMeterState;
import com.ruoyi.other.udan.model.TmsState;
import com.ruoyi.other.udan.model.UdanEmsDeviceState;
import com.ruoyi.other.udan.model.*;
import com.ruoyi.other.udan.util.UdanEmsKafkaUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
@@ -108,6 +102,9 @@
            else if (type == 2 && field == 8) state.setPowerMeterState(parsePowerMeter(in.readBytes().toByteArray()));
            else if (type == 2 && field == 14 && state.getTmsState() == null) {
                state.setTmsState(parseTms(in.readBytes().toByteArray()));
            }
            else if (type == 2 && field == 15 && state.getPeripheralDeviceState() == null) {
                state.setPeripheralDeviceState(parsePeripheral(in.readBytes().toByteArray()));
            }
            else skip(in, type);
        });
@@ -318,6 +315,19 @@
        return value;
    }
    private PeripheralDeviceState parsePeripheral(byte[] bytes) throws IOException {
        PeripheralDeviceState value = new PeripheralDeviceState();
        read(bytes, (field, type, input) -> {
            switch (field) {
                case 1: value.setDeviceId(str(input, type)); break;
                case 2: value.setDiState(u32(input, type)); break;
                default: skip(input, type);
            }
        });
        value.convert();
        return value;
    }
    private void read(byte[] bytes, Reader reader) throws IOException {
        CodedInputStream input = CodedInputStream.newInstance(bytes == null ? new byte[0] : bytes);
        while (!input.isAtEnd()) {
@@ -435,4 +445,5 @@
    private interface Reader {
        void read(int field, int type, CodedInputStream input) throws IOException;
    }
}
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/util/HuWeiUtils.java
@@ -47,7 +47,9 @@
    /** 登录华为 FusionSolar 并取得 XSRF-TOKEN。 */
    public String getXsrfToken() {
        TPlatformConfig config = platformConfigService.getOne(Wrappers.lambdaQuery(TPlatformConfig.class).eq(TPlatformConfig::getPlatformName, "华为"));
        TPlatformConfig config = platformConfigService.getOne(Wrappers.lambdaQuery(TPlatformConfig.class)
                .eq(TPlatformConfig::getType, 1)
                .eq(TPlatformConfig::getPlatformName, "华为"));
        if (config == null || StringUtils.isBlank(config.getAccount()) || StringUtils.isBlank(config.getPassword())) {
            return getXsrfToken(DEFAULT_USER_NAME, DEFAULT_SYSTEM_CODE);
        }