Ap01DemoTest.java
package com.fuiou.demo.api;

import cn.hutool.core.codec.Base64;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.http.HttpUtil;

import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/* 
 *  本代码仅供测试环境使用,不保证其完整性、正确性或适用于任何特定目的。 
 *  对于因使用本代码而造成的任何直接或间接损失,我们不承担任何责任。 
 *  建议您在生产环境中使用经过充分测试和验证的代码。 
 */
public class Ap01DemoTest {
    public static final String KEY_ALGORITHM = "RSA";
    public static final int KEY_SIZE = 2048; // 建议使用 2048 位或更长的密钥长度
    public static final String SIGNATURE_ALGORITHM = "SHA256withRSA";
    public static final String yourPrivateKey ="MIIEwAIBADANBgkqhkiG9w0BAQEFAASCBKowggSmAgEAAoIBAQCleZ/WWlI+MwaENt1OK1ykGMdP6hdca/14BfMnXxTo/8RelqDSkPuccYsn//v87YBejNYmL6CvsamLipXZNgpxvLjy3doJguFaN9L8HOe1E0YXNMgrSFiXMRyeE9lmSWt4Fe8c9TfhaGL+NqA8SKT/C3k/9J/L/PNQzEqzrJBpm3cVC+/EnNzMnVzVsfOmJWHgVB5YaBL8wnQjVTcuQiRXLP/AnFnIunx1SOvRyYBxWOFgu8GFspWgZqjAcCLKT9TS0uD10q7o11ekTkYRk7PfEPgKJ+Xa175dIpHcOZTdQO5ZMGT/6rXD+WSeYisPGGOW2cTa109Te0y44lCK7fJFAgMBAAECggEBAIxFfKQVbrBBSt3bMGCqS17jjlmFBAaZmIUc7hFK/YvB/LF+GJhGxLPKYH8o9XBj2DTOSF6YcytcfG/Iq9w0fkgKBfIC9GippOR4fAaxbg3GZ90WJjTioA6SWEL8aobV6B8k4Mx4ZsVSWtBKeCyCHDQDguYfNTKTm6K7evuyZbzO5HhKq0kP5+HQF7YphArBykRsS2pFjG+LgGeFpXVsdUljnLqa/go2gqLEkqF/BcL2fROA7VkS3QcG0WkgslugqDp9tS668+vdFFA4IXtuvPd6Skp6IQ2A9dv4EWS21wV8JOggY8UJ4YtDywE66fUNjrQBwKJm3bEL5r8AYppsfKUCgYEA+bPT4WdqR3OkLCZ+kZxW62bm+r/GLB22/ibaBJNSubtpQkhrEEEpMqJDyVpkqCjrNPDPhggGZOUJ/xURmcfEkiePKg0IsuRgqZCeE9QPfOnt/U9zg2U+fm+ESKPY6nl5ABqBjPR1pOBe5h3IOfWOA9ub1FE/+LRueJi5Ok7JDHsCgYEAqaX+UIvJ5ebwLdSw060TL+RTZ2MejjGPs+xCvEGmbSOT+PoP99P1ChktNX9OusqffIN4WGbbcgYXY1PtLypVmuFRMvqcv7VK8beP6eVfbBUuB9OSg1W5hwRQMkcbwz+pj9MiagjvBkH0z4QsRie9enI+aUk25pdDrwpfgB/FoD8CgYEAo8X2ahhR9Js8SljVGtvXhn3vcPbnG3hB1V/WDroxv+/Tkc29quOSCcuzehT3f/OWkRqAggAxcWtnqw4+hQYpP1MC6ymxUuPHIm/fvlGP9vXXShUaRkvZOUQbFymf0+noGtFHtxN/NayTkYpnENylUJJxGkhQFOcCrcY9dqjF5JECgYEAgsHnd4uXDTVnr9t8g1qmLEavJkPWnECFA2e5tEJhlUNT3RZYUmszNhpbpx09wGlGbgEjM/frckJRqoRYjv7xRlQecs2JHZYNcqtKKDxbxQG6Hdwr1ECxo+hmK6p1MpOSDMHuh43lNYyGtZ+pRFWDDKqbgiklQKwcRgEXxLg4aZ8CgYEA40iB/BgTHiesRcjj+nj1gog+/FwzL254lf+j1tfa7Ms2tf4L4hSo2rcDymSXnsxZFdZn3L8lKJg8jpxoNYihs9Jlr70FJDrSgOJ9WRq84ARwU/pnBR4H+9Y/u3Z5tKV8Mx5Xj4Rv0bdygltgaD8qlKbkJ7FfhmgmIAMYFNP7qAk=";
    public static final String fyPublicKey ="MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzj8g+wK2VhIMAMlSKSXh651qAMJnT1E9F6s/se+N9081whOotcFog3sdVF5SYTpfaKIeZ7gC53bWZFOtcz7WoXTaAG6xkCswp+CeKnX2wcj2yM2wjaQ8+tisy5Kj828jzTUHlvSVyDWCU+0XzpM0CvNRe5xw/1Wu4ET4frHxP0h041cfTlegfQPB0RjrP+qn2yTDaCSzuZ67qisJOswJ/OplSNeCPPNVXF9JTQaObB37Cc9NuONa3b5JXKBHy3lOgxNGvfrvFkKE+oiR+LaCq3a0vUMwLeIP10hgBm78GTGqF0QNoEL1liOT5KJG4GrwkdlMCRRq5FlTG5ocnSDSQwIDAQAB";

    static   String mchntSsn= DateUtil.currentSeconds() + RandomUtil.randomNumbers(3);

    /**
     * 生成rsa秘钥对
     * @return
     * @throws NoSuchAlgorithmException
     */
    public static KeyPair generateKeyPair() throws NoSuchAlgorithmException {
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KEY_ALGORITHM);
        keyPairGenerator.initialize(KEY_SIZE);
        return keyPairGenerator.generateKeyPair();
    }

    /**
     * rsa 签名
     * @param data
     * @param privateKey
     * @return
     * @throws Exception
     */
    public static String sign(byte[] data, String privateKey) throws Exception {
        byte[] keyBytes = Base64.decode(privateKey);
        PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        PrivateKey privateK = keyFactory.generatePrivate(pkcs8KeySpec);
        Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
        signature.initSign(privateK);
        signature.update(data);
        return Base64.encode(signature.sign());
    }

    /**
     * rsa 验签
     * @param publicKeyStr
     * @param data
     * @param signatureStr
     * @return
     */
    public static boolean verifySignature(String publicKeyStr, String data, String signatureStr) {
        try {
            byte[] publicKeyBytes = Base64.decode(publicKeyStr);
            X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKeyBytes);
            KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
            PublicKey publicKey = keyFactory.generatePublic(keySpec);
            Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
            signature.initVerify(publicKey);
            signature.update(data.getBytes("UTF-8"));
            byte[] signatureBytes = Base64.decode(signatureStr);
            return signature.verify(signatureBytes);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 获取xml对应节点的内容
     * @param xmlStr
     * @param nodeName
     * @return
     */
    public static String extractNodeValue(String xmlStr, String nodeName) {
        if(!xmlStr.contains("</"+nodeName+">")){
            return null;
        }
       return xmlStr.substring(xmlStr.indexOf("<"+nodeName+">")+nodeName.length()+2, xmlStr.indexOf("</"+nodeName+">"));
    }

    /**
     * 获取xml对应节点的结构字符串
     * @param xmlStr
     * @param nodeName
     * @return
     */
    public static String extractNodeFull(String xmlStr, String nodeName) {
        if(!xmlStr.contains("</"+nodeName+">")){
            return null;
        }
        return xmlStr.substring(xmlStr.indexOf("<"+nodeName+">"), xmlStr.lastIndexOf("</"+nodeName+">")+nodeName.length()+3);
    }

    /**
     * 4.1 付款(单笔)
     * @throws Exception
     */
    public static void payforreq() throws Exception {
        String xml = "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?><payforreq><ver>4.0</ver><merdt>"+ DateUtil.format(new Date(),"yyyyMMdd") +"</merdt>" +
                "<orderno>"+mchntSsn+"</orderno><accntno>6228480868777777777</accntno><accntnm>蒂花秀</accntnm>" +
                "<amt>100</amt><mobile>18888888888</mobile><addDesc>1</addDesc><sceneType>00</sceneType><memo>高温补贴</memo></payforreq>";
        String macSource ="0002900F0345178|"+"payforreq"+"|"+xml;
        final byte[] bytes = macSource.getBytes("UTF-8");
        Map<String,Object> postParams=new HashMap<>();
        postParams.put("merid", "0002900F0345178");
        postParams.put("reqtype", "payforreq");
        postParams.put("xml", xml);
        postParams.put("mac", sign(bytes, yourPrivateKey));
        System.out.println(xml);
        String result = HttpUtil.post("https://fht-test-api.fuioupay.com/fuMer_api/req.do", postParams);
        System.out.println(result);
        String ret = extractNodeValue(result,"ret");
        String memo =extractNodeValue(result,"memo");
        String mac = extractNodeValue(result,"mac");
        macSource="0002900F0345178|"+mchntSsn+"|"+ret+"|"+memo;
        System.out.println("验签: "+verifySignature(fyPublicKey,macSource,mac));
    }

    /**
     * 5.2 账户余额查
     * @throws Exception
     */
    public static void qryacnt() throws Exception {
        String macSource ="4.0|0002900F0345178";
        final byte[] bytes = macSource.getBytes("UTF-8");

        String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?><qryacnt>\n" +
                "  <ver>4.0</ver>\n" +
                "  <merid>0002900F0345178</merid>\n" +
                "  <mac>"+sign(bytes, yourPrivateKey)+"</mac>\n" +
                "</qryacnt>";

        Map<String,Object> postParams=new HashMap<>();
        postParams.put("merid", "0002900F0345178");
        postParams.put("xml", xml);
        String result = HttpUtil.post("https://fht-test-api.fuioupay.com/fuMer_api/t0zj_qryAcnt.do", postParams);
        System.out.println(result);
        String ret = extractNodeValue(result,"ret");
        String memo = extractNodeValue(result,"memo");
        String ctamt = extractNodeValue(result,"ctamt");
        String caamt = extractNodeValue(result,"caamt");
        String cuamt = extractNodeValue(result,"cuamt");
        String cfamt = extractNodeValue(result,"cfamt");
        String mac = extractNodeValue(result,"mac");
        macSource="0002900F0345178|"+ret+"|"+memo+"|"+ctamt+"|"+caamt+"|"+cuamt+"|"+cfamt;
        System.out.println("验签: "+verifySignature(fyPublicKey,macSource,mac));
    }

    /**
     * 5.1 交易查询(条件可组合)
     * @throws Exception
     */
    public static void qry() throws Exception {
        String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?><qrytransreq>\n" +
                "  <ver>4.0</ver>\n" +
                "  <busicd>AP01</busicd>\n" +
                "  <orderno></orderno>\n" +
                "  <startdt>20240924</startdt>\n" +
                "  <enddt>20240924</enddt>\n" +
                "</qrytransreq>";

        String macSource ="0002900F0345178|"+xml;
        final byte[] bytes = macSource.getBytes("UTF-8");
        Map<String,Object> postParams=new HashMap<>();
        postParams.put("merid", "0002900F0345178");
        postParams.put("xml", xml);
        postParams.put("mac", sign(bytes, yourPrivateKey));
        String result = HttpUtil.post("https://fht-test-api.fuioupay.com/fuMer_api/qry.do", postParams);
        System.out.println(result);

        String mac = extractNodeValue(result,"mac");
        String ret = extractNodeValue(result,"ret");
        String memo = extractNodeValue(result,"memo");
        if("000000".equals(ret)){
            macSource="0002900F0345178|"+ret+"|"+memo+"|"+ extractNodeFull(result,"trans");
        }else{
            macSource="0002900F0345178|"+ret+"|"+memo;
        }
        System.out.println("验签: "+verifySignature(fyPublicKey,macSource,mac));
    }

    /**
     * 生成 Base64 编码的字符串形式的rsa秘钥
     * @throws Exception
     */
    public static void genRsaKey() throws Exception {
        KeyPair keyPair = generateKeyPair();

        // 获取公钥和私钥
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();

        // 将密钥转换为 Base64 编码的字符串,方便存储和传输
        String publicKeyString = Base64.encode(publicKey.getEncoded());
        String privateKeyString = Base64.encode(privateKey.getEncoded());

        System.out.println("公钥 (Base64 编码):\n" + publicKeyString);
        System.out.println("私钥 (Base64 编码):\n" + privateKeyString);

        String message = "Hello, RSA!";
        String sign = sign(message.getBytes("UTF-8"), privateKeyString);
        System.out.println("检验密钥对是否匹配:"+verifySignature(publicKeyString,message,sign));

    }
    public static void main(String[] args) throws Exception {
        qryacnt();

    }
}