yupeng
2025-01-21 79e7583b387c83324be0e26a4dc16f97711b527b
新增银行接口模块
3个文件已修改
11个文件已添加
1101 ■■■■■ 已修改文件
bankapi/pom.xml 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
bankapi/src/main/java/com/taxi591/bankapi/BankConfig.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
bankapi/src/main/java/com/taxi591/bankapi/BankProperties.java 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
bankapi/src/main/java/com/taxi591/bankapi/dto/ChargeBillRequest.java 175 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
bankapi/src/main/java/com/taxi591/bankapi/dto/ChargeBillResponse.java 158 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
bankapi/src/main/java/com/taxi591/bankapi/dto/CovertPayBackResult.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
bankapi/src/main/java/com/taxi591/bankapi/service/BankService.java 108 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
bankapi/src/main/java/com/taxi591/bankapi/service/SignatureAndVerification.java 198 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
bankapi/src/main/java/com/taxi591/bankapi/utils/Base64.java 210 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
bankapi/src/main/resources/META-INF/spring.factories 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pom.xml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/pom.xml 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/com/ruoyi/web/controller/api/BankOutController.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelUtil.java 89 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
bankapi/pom.xml
New file
@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.ruoyi</groupId>
        <artifactId>ruoyi</artifactId>
        <version>3.8.6</version>
    </parent>
    <description>
        银行接口模块
    </description>
    <groupId>org.taxi591</groupId>
    <artifactId>bankapi</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <dependencies>
        <dependency>
            <groupId>com.ruoyi</groupId>
            <artifactId>ruoyi-framework</artifactId>
        </dependency>
        <dependency>
            <groupId>com.ruoyi</groupId>
            <artifactId>ruoyi-common</artifactId>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.5.15</version>
                <configuration>
                    <fork>true</fork> <!-- 如果没有该配置,devtools不会生效 -->
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>3.1.0</version>
                <configuration>
                    <failOnMissingWebXml>false</failOnMissingWebXml>
                    <warName>${project.artifactId}</warName>
                </configuration>
            </plugin>
        </plugins>
        <finalName>${project.artifactId}</finalName>
    </build>
</project>
bankapi/src/main/java/com/taxi591/bankapi/BankConfig.java
New file
@@ -0,0 +1,17 @@
package com.taxi591.bankapi;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableConfigurationProperties({BankProperties.class})
@ConditionalOnProperty(value = BankProperties.ENBALE_PREFIX, matchIfMissing = true)
@ComponentScan("com.taxi591.bankapi.service")
public class BankConfig {
}
bankapi/src/main/java/com/taxi591/bankapi/BankProperties.java
New file
@@ -0,0 +1,33 @@
package com.taxi591.bankapi;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import static com.taxi591.bankapi.BankProperties.PREFIX;
@Data
@ConfigurationProperties(prefix = PREFIX)
public class BankProperties {
    public static final String PREFIX = "com.taxi591.bank";
    public static final String ENBALE_PREFIX = PREFIX+".enable";
    private Boolean enable = true;
    /**
     * 证书路径
     */
    private String pfxPath;
    /**
     * 密码
     */
    private String keystorePassword;
    /**
     * 公钥路径
     */
    private String cerPath;
}
bankapi/src/main/java/com/taxi591/bankapi/dto/ChargeBillRequest.java
New file
@@ -0,0 +1,175 @@
package com.taxi591.bankapi.dto;
import com.alibaba.fastjson2.JSON;
import lombok.Data;
import java.io.Serializable;
/**
 *  直连商户平台缴费销账输入对象,需要转换成json串发送给第三方系统
 *  @author DELL
 *
 */
@Data
public class ChargeBillRequest implements Serializable {
    private static final long serialVersionUID = 1L;
    /** 格式 */
    private String format;
    /** 消息 */
    private Message message;
    @Override
    public String toString() {
        return "ChargeBillRequest[format=" + format + ",message=" + message.toString() + "]";
    }
    /**
     *
     * 账单查询内部消息对象实体message内部类
     *
     */
    @Data
    public class Message implements Serializable {
        private static final long serialVersionUID = 1L;
        /** 消息头部 */
        private Head head;
        /** 消息体  */
        private Info info;
        @Override
        public String toString() {
            return JSON.toJSONString(this);
        }
        /**
         *  message子对象head消息头内部类
         */
        @Data
        public class Head implements Serializable {
            private static final long serialVersionUID = 1L;
            /**  渠道编码 */
            private String channel;
            /**  交易码  */
            private String transCode;
            /**  交易上行下送标志位  */
            private String transFlag;
            /**  缴费中心交易序列号 */
            private String transSeqNum;
            /**   时间戳  */
            private String timeStamp;
            /**   4为分行iGoal码  */
            private String branchCode;
            @Override
            public String toString() {
                return JSON.toJSONString(this);
            }
        }
        /**
         * message子对象info消息实体内部类
         */
        @Data
        public class Info implements Serializable {
            private static final long serialVersionUID = 1L;
            /** 缴费项目编号*/
            private String epayCode;
            /** 第三方商户编号*/
            private String merchantId;
            /** 缴费中心流水号*/
            private String traceNo;
            /** 输入要素1*/
            private String input1;
            /** 输入要素2*/
            private String input2;
            /** 输入要素3*/
            private String input3;
            /** 输入要素4*/
            private String input4;
            /** 输入要素5*/
            private String input5;
            /** 农行16位客户号*/
            private String userId;
            /** 缴费金额计算规则*/
            private String amtRule;
            /** 合并支付的子账单数*/
            private String payBillCount;
            /** 合并支付的子账单累加总金额*/
            private String payBillAmt;
            /** 合并支付的子账单*/
            private String payBillNo;
            /** 套餐名称*/
            private String optionName;
            /** 套餐编码*/
            private String optionCode;
            /** 套餐金额*/
            private String optionAmt;
            /** 支付方式交易码*/
            private String payType;
            /** 缴费支付账号*/
            private String payAcc;
            /** 支付系统流水号*/
            private String transPaySeq;
            /** 支付系统日期*/
            private String transDate;
            /** 支付系统时间*/
            private String transTime;
            /** 会计日期*/
            private String settleDate;
            /** 清算模式*/
            private String clearType;
            /** 缓存域信息*/
            private String cacheMem;
            /** 销账报文重发次数,通过此字段识别销账报文是否为重发的,0表示首次、1表示重发一次,2表示重发2次,最多重发3次*/
            private String resendTimes;
            /** 第三方支付平台商户订单号 第三方平台例如微信支付宝的支付订单号 add 2020-01-13*/
            private String numOpenMerchantOrder;
            @Override
            public String toString() {
                return JSON.toJSONString(this);
            }
        }
    }
}
bankapi/src/main/java/com/taxi591/bankapi/dto/ChargeBillResponse.java
New file
@@ -0,0 +1,158 @@
package com.taxi591.bankapi.dto;
import com.alibaba.fastjson2.JSON;
import lombok.Data;
import java.io.Serializable;
/**
 *  直连商户平台账单销账返回对象
 *  @author DELL
 *
 */
@Data
public class ChargeBillResponse implements Serializable {
    private static final long serialVersionUID = 1L;
    /** 格式 */
    private String format;
    /** 消息体 */
    private Message message;
    public ChargeBillResponse(){
    }
    /**
     * 构造函数,通过输入对象,构造返回对象数据信息
     * @param request
     */
    public ChargeBillResponse(ChargeBillRequest request) {
        this.setFormat(request.getFormat());
        this.setMessage(new Message(request.getMessage()));
    }
    @Override
    public String toString() {
        return JSON.toJSONString(this);
    }
    /**
     *
     * 账单查询内部消息对象返回实体message内部类
     *
     */
    @Data
    public class Message implements Serializable {
        private static final long serialVersionUID = 1L;
        /** 消息头部 */
        private Head head;
        /** 消息实体  */
        private Info info;
        public Message() {
            this.head = new Head();
            this.info = new Info();
        }
        public Message(ChargeBillRequest.Message requestMessage){
            this.setHead(new Head(requestMessage.getHead()));
            this.setInfo(new Info(requestMessage.getInfo()));
        }
        @Override
        public String toString() {
            return JSON.toJSONString(this);
        }
        /**
         *
         * 账单销账内部消息对象返回实体Head内部类
         *
         */
        @Data
        public class Head implements Serializable {
            private static final long serialVersionUID = 1L;
            /**  渠道 */
            private String channel;
            /**  交易码  */
            private String transCode;
            /**  交易上行下送标志  */
            private String transFlag;
            /**  缴费中心交易序列号 */
            private String transSeqNum;
            /** 时间戳  */
            private String timeStamp;
            /**  查询返回码 */
            private String returnCode ;
            /**  返回提示信息  */
            private String returnMessage;
            public Head() {
            }
            public Head(ChargeBillRequest.Message.Head reqMessHead) {
                this.setChannel(reqMessHead.getChannel());
                this.setTransSeqNum(reqMessHead.getTransSeqNum());
                this.setTransCode(reqMessHead.getTransCode());
            }
            @Override
            public String toString() {
                return JSON.toJSONString(this);
            }
        }
        /**
         *
         * 账单查询内部消息对象返回实体Info内部类
         *
         */
        @Data
        public class Info implements Serializable {
            private static final long serialVersionUID = 1L;
            /** 缴费项目唯一标识号*/
            private String epayCode;
            /** 缴费中心流水号*/
            private String traceNo;
            /** 退款标志位*/
            private String refundFlag;
            /** 第三方支付平台商户订单号 第三方平台例如微信支付宝的支付订单号 add 2020-01-13*/
            private String numOpenMerchantOrder;
            public Info() {
            }
            public Info(ChargeBillRequest.Message.Info reqMessInfo) {
                this.setEpayCode(reqMessInfo.getEpayCode());
                this.setTraceNo(reqMessInfo.getTraceNo());
            }
            @Override
            public String toString() {
                return JSON.toJSONString(this);
            }
        }
    }
}
bankapi/src/main/java/com/taxi591/bankapi/dto/CovertPayBackResult.java
New file
@@ -0,0 +1,18 @@
package com.taxi591.bankapi.dto;
import lombok.Data;
import java.io.Serializable;
@Data
public class CovertPayBackResult implements Serializable {
    /**
     * 解析结果
     */
    private ChargeBillRequest result;
    /**
     * 返回银行应答内容
     */
    private String back;
}
bankapi/src/main/java/com/taxi591/bankapi/service/BankService.java
New file
@@ -0,0 +1,108 @@
package com.taxi591.bankapi.service;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.TypeReference;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.DateUtils;
import com.taxi591.bankapi.dto.ChargeBillRequest;
import com.taxi591.bankapi.dto.ChargeBillResponse;
import com.taxi591.bankapi.dto.CovertPayBackResult;
import com.taxi591.bankapi.utils.Base64;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletRequest;
import java.io.UnsupportedEncodingException;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@Service
@Slf4j
public class BankService {
    @Autowired
    SignatureAndVerification signatureAndVerification;
    static final String TIMESTAMP_PATTERN = "yyyyMMddHHmmssSSS";
    /**
     * 创建银行应答
     * @param request 银行请求对象
     * @param dealResult 是否处理成功
     * @return
     */
    public String createResponse(ChargeBillRequest request,Boolean dealResult){
        ChargeBillResponse response = new ChargeBillResponse(request);
        response.getMessage().getHead().setReturnCode(dealResult?"0000":"1111");
        response.getMessage().getHead().setReturnMessage(dealResult?"处理成功":"处理失败");
        response.getMessage().getInfo().setRefundFlag("false");
        response.getMessage().getHead().setTimeStamp(DateUtils.dateTimeNow(TIMESTAMP_PATTERN));
        if (!dealResult){
            if  (Integer.parseInt(request.getMessage().getInfo().getResendTimes())==3){
                response.getMessage().getInfo().setRefundFlag("true");
            }else{
                response.getMessage().getInfo().setRefundFlag("false");
            }
        }
        String respJSON = JSON.toJSONString(response);
        String sign = signatureAndVerification.signWhithsha1withrsa(respJSON);
        String respStr = null;
        try {
            respStr = sign + "||" + new String(org.apache.commons.codec.binary.Base64.encodeBase64(respJSON.getBytes("utf-8")));
        } catch (UnsupportedEncodingException e) {
        }
        return respStr;
    }
    /**
     * 处理支付回调数据
     * @param httpRequest  http请求对象
     * @param consumer 处理函数
     * @return
     */
    public CovertPayBackResult covertPayCallBack(HttpServletRequest httpRequest, Function<ChargeBillRequest,Boolean> consumer) {
        CovertPayBackResult result = new CovertPayBackResult();
        try {
            // 接收报文
            String requestContent = SignatureAndVerification.getRequestBody(httpRequest).trim();
            String sign = requestContent.substring(0,
                    requestContent.indexOf("||"));;
            String requestBody = requestContent.substring(sign
                    .length() + 2);;
            Pattern p=Pattern.compile("\"");
            Matcher m=p.matcher(requestBody);
            while(m.find()){
                requestBody=requestBody.replace(m.group(), "");
            }
            String request = new String(Base64.decodeFast(requestBody));
            log.info("-----ChargeBillController------------解析完成后的requestBody-------{}" + request);
            ChargeBillRequest chargeBillRequest = JSON.parseObject(request,
                    new TypeReference<ChargeBillRequest>() {
                    });
            if (chargeBillRequest==null){
                log.error("支付回调解析失败:{}",requestContent);
                throw new ServiceException("支付回调解析失败");
            }
            boolean isok = signatureAndVerification.read_cer_and_verify_sign(requestBody,sign);
            if (!isok){
                throw new ServiceException("支付回调验签失败");
            }
            Boolean dealBack = true;
            if (consumer!=null){
                dealBack = consumer.apply(chargeBillRequest);
            }
            result.setResult(chargeBillRequest);
            result.setBack(createResponse(chargeBillRequest,dealBack));
        }catch (Exception e){
            log.error("解析报文发生异常",e);
        }
        return result;
    }
}
bankapi/src/main/java/com/taxi591/bankapi/service/SignatureAndVerification.java
New file
@@ -0,0 +1,198 @@
package com.taxi591.bankapi.service;
import com.taxi591.bankapi.BankProperties;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import java.io.*;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
/**
 * 验签和加签工具类
 * @author yzz
 *
 */
@Component
@Slf4j
public class SignatureAndVerification {
    @Autowired
    BankProperties bankProperties;
    public static String getRequestBody(HttpServletRequest request)
            throws IOException {
        /** 读取httpbody内容 */
        StringBuilder httpBody = new StringBuilder();
        BufferedReader br = null;
        try {
            br = new BufferedReader(new InputStreamReader(
                    request.getInputStream()));
            String line = null;
            while ((line = br.readLine()) != null) {
                httpBody.append(line);
            }
        } catch (IOException ex) {
            throw ex;
        } finally {
            if (br != null) {
                try {
                    br.close();
                } catch (IOException ex) {
                    ex.printStackTrace();
                }
            }
        }
        return httpBody.toString();
    }
    /**
     * 加签名
     * @param dataString
     * @return
     */
    public  String signWhithsha1withrsa(String dataString) {
        String signatureString = null;
        String filePath=bankProperties.getPfxPath();
        try {
            KeyStore ks = KeyStore.getInstance("PKCS12");
            FileInputStream fis = new FileInputStream(filePath);
            char[] nPassword = null;
            if ((bankProperties.getKeystorePassword() == null)
                    || bankProperties.getKeystorePassword().trim().equals("")) {
                nPassword = null;
            } else {
                nPassword = bankProperties.getKeystorePassword().toCharArray();
            }
            ks.load(fis, nPassword);
            fis.close();
            Enumeration<String> enums = ks.aliases();
            String keyAlias = null;
            if (enums.hasMoreElements())
            {
                keyAlias = (String) enums.nextElement();
            }
            System.out.println("is key entry=" + ks.isKeyEntry(keyAlias));
            PrivateKey prikey = (PrivateKey) ks.getKey(keyAlias, nPassword);
            java.security.cert.Certificate cert = ks.getCertificate(keyAlias);
            // SHA1withRSA算法进行签名
            Signature sign = Signature.getInstance("SHA1withRSA");
            sign.initSign(prikey);
            byte[] data = dataString.getBytes("utf-8");
            byte[] dataBase= Base64.encodeBase64(data);
            // 更新用于签名的数据
            sign.update(dataBase);
            byte[] signature = sign.sign();
            signatureString = new String(Base64.encodeBase64(signature));
            System.out.println("加密完成,signature is : " + signatureString);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return signatureString;
    }
    /**
     * 读取cer并验证公钥签名
     * @return
     */
    public  boolean read_cer_and_verify_sign(String requestBody, String signature) {
        String filePath=bankProperties.getCerPath();
        X509Certificate cert = null;
        boolean flag = false;
        try {
            CertificateFactory cf = CertificateFactory.getInstance("X.509");
            cert = (X509Certificate) cf
                    .generateCertificate(new FileInputStream(new File(
                            filePath)));
            PublicKey publicKey = cert.getPublicKey();
            String publicKeyString = new String(Base64.encodeBase64(publicKey
                    .getEncoded()));
            System.out.println("-----------------公钥--------------------");
            System.out.println(publicKeyString);
            System.out.println("-----------------公钥--------------------");
            Signature verifySign = Signature.getInstance("SHA1withRSA");
            verifySign.initVerify(publicKey);
            // 用于验签的数据
            verifySign.update(requestBody.getBytes("utf-8"));
            flag = verifySign.verify(Base64
                    .decodeBase64(signature));
        } catch (Exception e) {
            log.error("验签失败,发生异常:{},{}",requestBody,signature,e);
            flag = false;
        }
        return flag;
    }
    /**
     * 接收报文返回requestBody和使用base64解析后的requestBody以及缴费中心传送的签名
     */
    public Map<String,String> requestBodyOfBase64(HttpServletRequest request){
        Map<String,String> requestMap=new HashMap<String,String>();
        // 接收报文
        String requestContent=null;
        try {
            requestContent = getRequestBody(request)
                    .trim();
        } catch (IOException e) {
            e.printStackTrace();
        }
        String signatureString = null;
        String requestBody = null;
        if (requestContent.contains("||")) {
            signatureString = requestContent.substring(0,
                    requestContent.indexOf("||"));
            requestBody = requestContent.substring(signatureString
                    .length() + 2);
        }else {
            try {
                requestBody = new String(requestContent.getBytes("GB2312"));
                log.info("转码后的报文:{}",requestBody);
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
        }
        log.info("截取报文的requestBody解密前:{}", requestBody);
        String requestBodyOfDecoded = new String(
                Base64.decodeBase64(requestBody));
        /*if (requestBodyOfDecoded.contains("</Message>")) {
            requestBody = requestBodyOfDecoded.substring(
                    requestContent.indexOf("</Message>"),requestContent.indexOf("</Message>")+10);
            signatureString = requestBodyOfDecoded.substring(
                    requestContent.indexOf("<Signature>")+11,requestContent.indexOf("</Signature>"));
        }*/
        log.info("截取报文的signatureString:{}", signatureString);
        System.out.println("-----解析完成后的requestBody-------" + requestBodyOfDecoded);
        //使用base64解析完成后的requestBody
        requestMap.put("requestBodyOfDecoded",requestBodyOfDecoded);
        //解析前的requestBody
        requestMap.put("requestBody",requestBody);
        //获取缴费中心传送过来的签名
        requestMap.put("signatureString",signatureString);
        return requestMap;
    }
}
bankapi/src/main/java/com/taxi591/bankapi/utils/Base64.java
New file
@@ -0,0 +1,210 @@
package com.taxi591.bankapi.utils;
import java.util.Arrays;
/**
 *
 * @version 2.2
 * @author Mikael Grev Date: 2004-aug-02 Time: 11:31:11
 */
public class Base64 {
    public static final char[] CA = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".toCharArray();
    public static final int[]  IA = new int[256];
    static {
        Arrays.fill(IA, -1);
        for (int i = 0, iS = CA.length; i < iS; i++)
            IA[CA[i]] = i;
        IA['='] = 0;
    }
    /**
     * Decodes a BASE64 encoded char array that is known to be resonably well formatted. The method is about twice as
     * fast as #decode(char[]). The preconditions are:<br>
     * + The array must have a line length of 76 chars OR no line separators at all (one line).<br>
     * + Line separator must be "\r\n", as specified in RFC 2045 + The array must not contain illegal characters within
     * the encoded string<br>
     * + The array CAN have illegal characters at the beginning and end, those will be dealt with appropriately.<br>
     *
     * @param chars The source array. Length 0 will return an empty array. <code>null</code> will throw an exception.
     * @return The decoded array of bytes. May be of length 0.
     */
    public static byte[] decodeFast(char[] chars, int offset, int charsLen) {
        // Check special case
        if (charsLen == 0) {
            return new byte[0];
        }
        int sIx = offset, eIx = offset + charsLen - 1; // Start and end index after trimming.
        // Trim illegal chars from start
        while (sIx < eIx && IA[chars[sIx]] < 0)
            sIx++;
        // Trim illegal chars from end
        while (eIx > 0 && IA[chars[eIx]] < 0)
            eIx--;
        // get the padding count (=) (0, 1 or 2)
        int pad = chars[eIx] == '=' ? (chars[eIx - 1] == '=' ? 2 : 1) : 0; // Count '=' at end.
        int cCnt = eIx - sIx + 1; // Content count including possible separators
        int sepCnt = charsLen > 76 ? (chars[76] == '\r' ? cCnt / 78 : 0) << 1 : 0;
        int len = ((cCnt - sepCnt) * 6 >> 3) - pad; // The number of decoded bytes
        byte[] bytes = new byte[len]; // Preallocate byte[] of exact length
        // Decode all but the last 0 - 2 bytes.
        int d = 0;
        for (int cc = 0, eLen = (len / 3) * 3; d < eLen;) {
            // Assemble three bytes into an int from four "valid" characters.
            int i = IA[chars[sIx++]] << 18 | IA[chars[sIx++]] << 12 | IA[chars[sIx++]] << 6 | IA[chars[sIx++]];
            // Add the bytes
            bytes[d++] = (byte) (i >> 16);
            bytes[d++] = (byte) (i >> 8);
            bytes[d++] = (byte) i;
            // If line separator, jump over it.
            if (sepCnt > 0 && ++cc == 19) {
                sIx += 2;
                cc = 0;
            }
        }
        if (d < len) {
            // Decode last 1-3 bytes (incl '=') into 1-3 bytes
            int i = 0;
            for (int j = 0; sIx <= eIx - pad; j++)
                i |= IA[chars[sIx++]] << (18 - j * 6);
            for (int r = 16; d < len; r -= 8)
                bytes[d++] = (byte) (i >> r);
        }
        return bytes;
    }
    public static byte[] decodeFast(String chars, int offset, int charsLen) {
        // Check special case
        if (charsLen == 0) {
            return new byte[0];
        }
        int sIx = offset, eIx = offset + charsLen - 1; // Start and end index after trimming.
        // Trim illegal chars from start
        while (sIx < eIx && IA[chars.charAt(sIx)] < 0)
            sIx++;
        // Trim illegal chars from end
        while (eIx > 0 && IA[chars.charAt(eIx)] < 0)
            eIx--;
        // get the padding count (=) (0, 1 or 2)
        int pad = chars.charAt(eIx) == '=' ? (chars.charAt(eIx - 1) == '=' ? 2 : 1) : 0; // Count '=' at end.
        int cCnt = eIx - sIx + 1; // Content count including possible separators
        int sepCnt = charsLen > 76 ? (chars.charAt(76) == '\r' ? cCnt / 78 : 0) << 1 : 0;
        int len = ((cCnt - sepCnt) * 6 >> 3) - pad; // The number of decoded bytes
        byte[] bytes = new byte[len]; // Preallocate byte[] of exact length
        // Decode all but the last 0 - 2 bytes.
        int d = 0;
        for (int cc = 0, eLen = (len / 3) * 3; d < eLen;) {
            // Assemble three bytes into an int from four "valid" characters.
            int i = IA[chars.charAt(sIx++)] << 18 | IA[chars.charAt(sIx++)] << 12 | IA[chars.charAt(sIx++)] << 6 | IA[chars.charAt(sIx++)];
            // Add the bytes
            bytes[d++] = (byte) (i >> 16);
            bytes[d++] = (byte) (i >> 8);
            bytes[d++] = (byte) i;
            // If line separator, jump over it.
            if (sepCnt > 0 && ++cc == 19) {
                sIx += 2;
                cc = 0;
            }
        }
        if (d < len) {
            // Decode last 1-3 bytes (incl '=') into 1-3 bytes
            int i = 0;
            for (int j = 0; sIx <= eIx - pad; j++)
                i |= IA[chars.charAt(sIx++)] << (18 - j * 6);
            for (int r = 16; d < len; r -= 8)
                bytes[d++] = (byte) (i >> r);
        }
        return bytes;
    }
    /**
     * Decodes a BASE64 encoded string that is known to be resonably well formatted. The method is about twice as fast
     * as decode(String). The preconditions are:<br>
     * + The array must have a line length of 76 chars OR no line separators at all (one line).<br>
     * + Line separator must be "\r\n", as specified in RFC 2045 + The array must not contain illegal characters within
     * the encoded string<br>
     * + The array CAN have illegal characters at the beginning and end, those will be dealt with appropriately.<br>
     *
     * @param s The source string. Length 0 will return an empty array. <code>null</code> will throw an exception.
     * @return The decoded array of bytes. May be of length 0.
     */
    public static byte[] decodeFast(String s) {
        // Check special case
        int sLen = s.length();
        if (sLen == 0) {
            return new byte[0];
        }
        int sIx = 0, eIx = sLen - 1; // Start and end index after trimming.
        // Trim illegal chars from start
        while (sIx < eIx && IA[s.charAt(sIx) & 0xff] < 0)
            sIx++;
        // Trim illegal chars from end
        while (eIx > 0 && IA[s.charAt(eIx) & 0xff] < 0)
            eIx--;
        // get the padding count (=) (0, 1 or 2)
        int pad = s.charAt(eIx) == '=' ? (s.charAt(eIx - 1) == '=' ? 2 : 1) : 0; // Count '=' at end.
        int cCnt = eIx - sIx + 1; // Content count including possible separators
        int sepCnt = sLen > 76 ? (s.charAt(76) == '\r' ? cCnt / 78 : 0) << 1 : 0;
        int len = ((cCnt - sepCnt) * 6 >> 3) - pad; // The number of decoded bytes
        byte[] dArr = new byte[len]; // Preallocate byte[] of exact length
        // Decode all but the last 0 - 2 bytes.
        int d = 0;
        for (int cc = 0, eLen = (len / 3) * 3; d < eLen;) {
            // Assemble three bytes into an int from four "valid" characters.
            int i = IA[s.charAt(sIx++)] << 18 | IA[s.charAt(sIx++)] << 12 | IA[s.charAt(sIx++)] << 6
                    | IA[s.charAt(sIx++)];
            // Add the bytes
            dArr[d++] = (byte) (i >> 16);
            dArr[d++] = (byte) (i >> 8);
            dArr[d++] = (byte) i;
            // If line separator, jump over it.
            if (sepCnt > 0 && ++cc == 19) {
                sIx += 2;
                cc = 0;
            }
        }
        if (d < len) {
            // Decode last 1-3 bytes (incl '=') into 1-3 bytes
            int i = 0;
            for (int j = 0; sIx <= eIx - pad; j++)
                i |= IA[s.charAt(sIx++)] << (18 - j * 6);
            for (int r = 16; d < len; r -= 8)
                dArr[d++] = (byte) (i >> r);
        }
        return dArr;
    }
}
bankapi/src/main/resources/META-INF/spring.factories
New file
@@ -0,0 +1,2 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.taxi591.bankapi.BankConfig
pom.xml
@@ -187,6 +187,7 @@
        <module>ruoyi-generator</module>
        <module>ruoyi-common</module>
        <module>ruoyi-applet</module>
        <module>bankapi</module>
    </modules>
    <packaging>pom</packaging>
ruoyi-admin/pom.xml
@@ -16,7 +16,11 @@
    </description>
    <dependencies>
        <dependency>
            <groupId>org.taxi591</groupId>
            <artifactId>bankapi</artifactId>
            <version>1.0.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>com.aliyun.oss</groupId>
            <artifactId>aliyun-sdk-oss</artifactId>
ruoyi-admin/src/main/java/com/ruoyi/web/controller/api/BankOutController.java
New file
@@ -0,0 +1,30 @@
package com.ruoyi.web.controller.api;
import com.taxi591.bankapi.dto.CovertPayBackResult;
import com.taxi591.bankapi.service.BankService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
@RestController
@RequestMapping("out/bank")
public class BankOutController {
    @Autowired
    BankService bankService;
    @PostMapping(value = "payCallback")
    public @ResponseBody String payCallback(HttpServletRequest request){
        CovertPayBackResult result = bankService.covertPayCallBack(request, (billRequest) -> {
            return true;
        });
        return result.getBack();
    }
}
ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelUtil.java
@@ -1,93 +1,10 @@
package com.ruoyi.common.utils.poi;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletResponse;
import com.sun.rowset.internal.Row;
import javafx.scene.control.Cell;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.RegExUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
//import org.apache.poi.hssf.usermodel.HSSFClientAnchor;
//import org.apache.poi.hssf.usermodel.HSSFPicture;
//import org.apache.poi.hssf.usermodel.HSSFPictureData;
//import org.apache.poi.hssf.usermodel.HSSFShape;
//import org.apache.poi.hssf.usermodel.HSSFSheet;
//import org.apache.poi.hssf.usermodel.HSSFWorkbook;
//import org.apache.poi.ooxml.POIXMLDocumentPart;
//import org.apache.poi.ss.usermodel.BorderStyle;
//import org.apache.poi.ss.usermodel.Cell;
//import org.apache.poi.ss.usermodel.CellStyle;
//import org.apache.poi.ss.usermodel.CellType;
//import org.apache.poi.ss.usermodel.ClientAnchor;
//import org.apache.poi.ss.usermodel.DataValidation;
//import org.apache.poi.ss.usermodel.DataValidationConstraint;
//import org.apache.poi.ss.usermodel.DataValidationHelper;
//import org.apache.poi.ss.usermodel.DateUtil;
//import org.apache.poi.ss.usermodel.Drawing;
//import org.apache.poi.ss.usermodel.FillPatternType;
//import org.apache.poi.ss.usermodel.Font;
//import org.apache.poi.ss.usermodel.HorizontalAlignment;
//import org.apache.poi.ss.usermodel.IndexedColors;
//import org.apache.poi.ss.usermodel.Name;
//import org.apache.poi.ss.usermodel.PictureData;
//import org.apache.poi.ss.usermodel.Row;
//import org.apache.poi.ss.usermodel.Sheet;
//import org.apache.poi.ss.usermodel.VerticalAlignment;
//import org.apache.poi.ss.usermodel.Workbook;
//import org.apache.poi.ss.usermodel.WorkbookFactory;
//import org.apache.poi.ss.util.CellRangeAddress;
//import org.apache.poi.ss.util.CellRangeAddressList;
//import org.apache.poi.util.IOUtils;
//import org.apache.poi.xssf.streaming.SXSSFWorkbook;
//import org.apache.poi.xssf.usermodel.XSSFClientAnchor;
//import org.apache.poi.xssf.usermodel.XSSFDataValidation;
//import org.apache.poi.xssf.usermodel.XSSFDrawing;
//import org.apache.poi.xssf.usermodel.XSSFPicture;
//import org.apache.poi.xssf.usermodel.XSSFShape;
//import org.apache.poi.xssf.usermodel.XSSFSheet;
//import org.apache.poi.xssf.usermodel.XSSFWorkbook;
//import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.CTMarker;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
//import com.ruoyi.common.annotation.Excel;
//import com.ruoyi.common.annotation.Excel.ColumnType;
//import com.ruoyi.common.annotation.Excel.Type;
import com.ruoyi.common.annotation.Excels;
import com.ruoyi.common.config.RuoYiConfig;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.text.Convert;
import com.ruoyi.common.exception.UtilException;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.DictUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.file.FileTypeUtils;
import com.ruoyi.common.utils.file.FileUtils;
import com.ruoyi.common.utils.file.ImageUtils;
import com.ruoyi.common.utils.reflect.ReflectUtils;
import java.util.HashMap;
import java.util.Map;
/**
 * Excel相关处理