# AdjTrxCodeConfigServiceImpl.java

    package cloud.wconnect.frontoffice.service.adjustment.impl;

    import cloud.wconnect.commonlib.component.BeanMapper;
    import cloud.wconnect.commonlib.cons.BaseConstant;
    import cloud.wconnect.commonlib.cons.BaseErrorCodeEnum;
    import cloud.wconnect.commonlib.cons.CommonErrorI18nConstant;
    import cloud.wconnect.commonlib.exception.BadRequestException;
    import cloud.wconnect.commonlib.pojo.dto.ListPagedDTO;
    import cloud.wconnect.commonlib.util.JsonUtils;
    import cloud.wconnect.frontoffice.cons.adjustment.TrxCodeTypeEnum;
    import cloud.wconnect.frontoffice.entity.adjustment.AdjTrxCodeConfig;
    import cloud.wconnect.frontoffice.mapper.adjustment.AdjTrxCodeConfigMapper;
    import cloud.wconnect.frontoffice.pojo.dto.adjustment.AdjTrxCodeConfigDTO;
    import cloud.wconnect.frontoffice.pojo.dto.adjustment.ScopeMatchDTO;
    import cloud.wconnect.frontoffice.pojo.query.adjustment.TrxCodeQuery;
    import cloud.wconnect.frontoffice.service.adjustment.IAdjTrxCodeConfigService;
    import com.baomidou.dynamic.datasource.annotation.DS;
    import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
    import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.google.common.collect.Lists;
    import org.apache.commons.collections.CollectionUtils;
    import org.apache.commons.lang3.StringUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;

    import java.util.Collections;
    import java.util.List;
    import java.util.Map;
    import java.util.Objects;
    import java.util.stream.Collectors;

    /**
     * <p>
     * Adjustment Requests trxCode 忽略配置表 服务实现类
     * </p>
     *
     * @author lichen
     * @since 2023-04-04
     */
    @Service
    @DS("logbook")
    public class AdjTrxCodeConfigServiceImpl extends ServiceImpl<AdjTrxCodeConfigMapper, AdjTrxCodeConfig> implements IAdjTrxCodeConfigService {

        @Autowired
        private BeanMapper beanMapper;
        @Autowired
        private ObjectMapper objectMapper;

        /**
         * 查询指定资产忽略的trx code集合
         *
         * @param propertyId 资产ID
         * @return java.util.List<cloud.wconnect.frontoffice.entity.adjustment.AdjTrxCodeConfig>
         * @author lichen
         * @date 2024/7/26
         */
        @Override
        public List<AdjTrxCodeConfig> listByPropertyId(Integer propertyId) {
            List<AdjTrxCodeConfig> list = list(new LambdaQueryWrapper<AdjTrxCodeConfig>()
                    .eq(AdjTrxCodeConfig::getPropertyId, propertyId));
            if (CollectionUtils.isEmpty(list)) return Collections.emptyList();
            return list;
        }

        /**
         * 判断trxCode是否配置为忽略
         *
         * @param adjTrxCodeConfigs 配置为忽略的trxCode集合
         * @param trxCode           交易代码
         * @return boolean true:忽略 false:不忽略
         * @author lichen
         * @date 2024/7/26
         */
        @Override
        public boolean isIgnore(List<AdjTrxCodeConfig> adjTrxCodeConfigs, String trxCode) {
            if (CollectionUtils.isEmpty(adjTrxCodeConfigs)) return false;

            List<AdjTrxCodeConfigDTO> dtoList = convertToDTO(adjTrxCodeConfigs);
            Map<Integer, List<AdjTrxCodeConfigDTO>> matchType2DTOList = dtoList.stream().collect(Collectors.groupingBy(AdjTrxCodeConfigDTO::getMatchType));

            //精确匹配
            List<String> exactMatchList = matchType2DTOList.getOrDefault(TrxCodeTypeEnum.EXACT_MATCH.getType(), Collections.emptyList())
                    .stream()
                    .map(AdjTrxCodeConfigDTO::getTrxCode).toList();
            if (exactMatchList.contains(trxCode)) {
                return true;
            }

            //左模糊匹配
            List<String> leftmostFuzzyMatchList = matchType2DTOList.getOrDefault(TrxCodeTypeEnum.LEFTMOST_FUZZY_MATCH.getType(), Collections.emptyList())
                    .stream()
                    .map(AdjTrxCodeConfigDTO::getTrxCode).toList();
            if (leftmostFuzzyMatchList.stream().anyMatch(trxCode::startsWith)) {
                return true;
            }

            Integer trxCodeInteger = isInteger(trxCode);
            if (Objects.nonNull(trxCodeInteger)) {
                //范围匹配
                List<AdjTrxCodeConfigDTO> rangeMatchList = matchType2DTOList.getOrDefault(TrxCodeTypeEnum.RANGE_MATCH.getType(), Collections.emptyList());
                for (AdjTrxCodeConfigDTO config : rangeMatchList) {
                    ScopeMatchDTO scopeMatchDTO = config.getScopeMatch();
                    //trxCode 在范围内，则返回true表示忽略
                    if (trxCodeInteger.compareTo(scopeMatchDTO.getStart()) >= 0 && trxCodeInteger.compareTo(scopeMatchDTO.getEnd()) <= 0) {
                        return true;
                    }
                }
            }

            //带前缀的范围匹配
            List<AdjTrxCodeConfigDTO> rangeMatchList = matchType2DTOList.getOrDefault(TrxCodeTypeEnum.RANGE_MATCH_WITH_PREFIX.getType(), Collections.emptyList());
            for (AdjTrxCodeConfigDTO adjTrxCodeConfigDTO : rangeMatchList) {
                ScopeMatchDTO scopeMatchDTO = adjTrxCodeConfigDTO.getScopeMatch();
                //前缀
                String prefix = scopeMatchDTO.getPrefix();
                //判断trxCode是否指定前缀开头
                if (trxCode.startsWith(prefix)) {
                    //去除掉指定前缀的部分trxCode
                    String partTrxCode = trxCode.substring(prefix.length());
                    Integer partTrxCodeInteger = isInteger(partTrxCode);
                    if (Objects.nonNull(partTrxCodeInteger)) {
                        //trxCode 在范围内，则返回true表示忽略
                        if (partTrxCodeInteger.compareTo(scopeMatchDTO.getStart()) >= 0 && partTrxCodeInteger.compareTo(scopeMatchDTO.getEnd()) <= 0) {
                            return true;
                        }
                    }
                }
            }
            return false;
        }

        /**
         * 分页查询
         *
         * @param propertyId   资产ID
         * @param trxCodeQuery 查询对象
         * @return cloud.wconnect.commonlib.pojo.dto.ListPagedDTO<cloud.wconnect.frontoffice.pojo.dto.adjustment.AdjTrxCodeConfigDTO>
         * @author lichen
         * @date 2025/8/22
         */
        @Override
        public ListPagedDTO<AdjTrxCodeConfigDTO> pageList(int propertyId, TrxCodeQuery trxCodeQuery) {
            // 构造查询条件
            LambdaQueryWrapper<AdjTrxCodeConfig> lambdaQueryWrapper = new LambdaQueryWrapper<AdjTrxCodeConfig>()
                    .eq(AdjTrxCodeConfig::getPropertyId, propertyId)
                    .orderByDesc(AdjTrxCodeConfig::getId);
            if (Objects.nonNull(trxCodeQuery.getMatchType())) {
                lambdaQueryWrapper.eq(AdjTrxCodeConfig::getMatchType, trxCodeQuery.getMatchType());

                if (StringUtils.isNotBlank(trxCodeQuery.getContent())) {
                    if (trxCodeQuery.getMatchType() == TrxCodeTypeEnum.EXACT_MATCH.getType()
                            || trxCodeQuery.getMatchType() == TrxCodeTypeEnum.LEFTMOST_FUZZY_MATCH.getType()) {
                        // 精确匹配 or 最左模糊匹配
                        lambdaQueryWrapper.eq(AdjTrxCodeConfig::getTrxCode, trxCodeQuery.getContent());
                    } else {
                        // 范围匹配 or 范围匹配+前缀
                        lambdaQueryWrapper.apply("JSON_CONTAINS(trx_Code, {0}, '$')", trxCodeQuery.getContent());
                    }
                }
            }

            // 判断是否分页查询
            boolean isPage = Objects.nonNull(trxCodeQuery.getPage()) && Objects.nonNull(trxCodeQuery.getSize());

            List<AdjTrxCodeConfig> records;
            long total;
            if (isPage) {
                Page<AdjTrxCodeConfig> resultPage = page(new Page<>(trxCodeQuery.getPage(), trxCodeQuery.getSize()), lambdaQueryWrapper);
                total = resultPage.getTotal();
                records = resultPage.getRecords();
            } else {
                records = list(lambdaQueryWrapper);
                total = records.size();
            }

            if (total == BaseConstant.ZERO) {
                return new ListPagedDTO<>();
            }
            return new ListPagedDTO<>(total, convertToDTO(records));
        }

        /**
         * 校验存在
         *
         * @param propertyId 资产ID
         * @param matchType  匹配类型
         * @param trxCode    交易代码
         * @author lichen
         * @date 2026/1/29
         */
        @Override
        public void checkExist(int propertyId, Integer matchType, String trxCode) {
            // 构造查询条件
            LambdaQueryWrapper<AdjTrxCodeConfig> lambdaQueryWrapper = new LambdaQueryWrapper<AdjTrxCodeConfig>()
                    .eq(AdjTrxCodeConfig::getPropertyId, propertyId)
                    .eq(AdjTrxCodeConfig::getMatchType, matchType);
            if (matchType == TrxCodeTypeEnum.EXACT_MATCH.getType()
                    || matchType == TrxCodeTypeEnum.LEFTMOST_FUZZY_MATCH.getType()) {
                // 精确匹配 or 最左模糊匹配
                lambdaQueryWrapper.eq(AdjTrxCodeConfig::getTrxCode, trxCode);
            } else {
                // 范围匹配 or 范围匹配+前缀
                lambdaQueryWrapper.apply("JSON_CONTAINS(trx_Code, {0}, '$')", trxCode);
            }

            if (count(lambdaQueryWrapper) > 0) {
                throw new BadRequestException(CommonErrorI18nConstant.DATA_EXISTED,
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(), "trxCode existing data, trxCode=" + trxCode);
            }
        }

        /**
         * 转换为DTO
         *
         * @param adjTrxCodeConfigs 配置集合
         * @return java.util.List<cloud.wconnect.frontoffice.pojo.dto.adjustment.AdjTrxCodeConfigDTO>
         * @author lichen
         * @date 2025/8/23
         */
        private List<AdjTrxCodeConfigDTO> convertToDTO(List<AdjTrxCodeConfig> adjTrxCodeConfigs) {
            if (CollectionUtils.isEmpty(adjTrxCodeConfigs)) return Collections.emptyList();
            return beanMapper.mapList(adjTrxCodeConfigs, AdjTrxCodeConfigDTO.class, (config, dto) -> {
                if (Lists.newArrayList(TrxCodeTypeEnum.RANGE_MATCH.getType(), TrxCodeTypeEnum.RANGE_MATCH_WITH_PREFIX.getType())
                        .contains(dto.getMatchType())) {
                    ScopeMatchDTO scopeMatchDTO = JsonUtils.convertStringToObject(objectMapper, dto.getTrxCode(), ScopeMatchDTO.class);
                    dto.setScopeMatch(scopeMatchDTO);
                }
            });
        }

        /**
         * 判断trxCode是否为int类型
         *
         * @param trxCode 交易代码
         * @return java.lang.Integer
         * @author lichen
         * @date 2025/8/23
         */
        private Integer isInteger(String trxCode) {
            Integer trxCodeInt;
            try {
                trxCodeInt = Integer.valueOf(trxCode);
            } catch (NumberFormatException e) {
                trxCodeInt = null;
            }
            return trxCodeInt;
        }
    }