package com.bohuikeji.frame.module.admin.wxpayv3;
import com.bohuikeji.frame.module.admin.component.StaticComponent;
import com.bohuikeji.frame.module.admin.entity.JSAPI.Amount;
import com.bohuikeji.frame.module.admin.entity.JSAPI.Payer;
import com.bohuikeji.frame.module.admin.entity.JSAPI.SceneInfo;
import com.bohuikeji.frame.module.admin.entity.JSAPI.WxPayBo;
import com.bohuikeji.frame.module.admin.utils.FileUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.ContentType;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSONObject;
import com.bohuikeji.frame.module.admin.entity.vo.WXPayVo;
import com.bohuikeji.frame.module.admin.utils.IpUtil;
import com.ijpay.core.kit.PayKit;
import com.ijpay.core.kit.RsaKit;
import lombok.extern.slf4j.Slf4j;
import okhttp3.HttpUrl;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.cert.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.*;

import static com.ijpay.core.kit.PayKit.urlEncode;
/**
 * 〈一句话功能简述〉<br>
 * 〈微信支付V3请求〉
 *
 * @author Gym
 * @create 2020/12/18
 * @since 1.0.0
 */
@Slf4j
public class WxPayV3Util {

    private static final String url_prex = "https://api.mch.weixin.qq.com/";//微信V3版本支付接口请求URL
    //private static final String url_refund = "https://api.mch.weixin.qq.com/";//微信V3版本退款接口请求URL
    private static final String authType = "WECHATPAY2-SHA256-RSA2048";
    private static final String ApiV3Key = "2rbtb16aabm2ucfpzlf9nem3gcwohz83";
    private static final String privateKeyFilePath = "https://oss.tcjskd.com/delivery/upload/pay/wx/apiclient_key.pem";//标准证书的密钥
    private static final String privateFilePath = "https://oss.tcjskd.com/delivery/upload/pay/wx/apiclient_cert.pem";//标准的证书


    /**
     * 】客户端小程序
     * 微信V3版本支付下单
     * @param url 小程序支付请求接口路径
     * @param outTradeNo 订单号
     * @param total 订单支付金额
     * @param openid 客户的openId
     * @param desc 商
     * @param request
     * @return 返回预支付订单标识
     * @throws Exception
     */
    public static String V3JsapiPay(String url, String outTradeNo, BigDecimal total,String openid,String desc,HttpServletRequest request) throws Exception {
        log.info("金额是多少 [{}]",total);
        log.info("微信OPENID [{}]",openid);
        WxPayBo wxPayBo = new WxPayBo()
                .setAppid(WXPayVo.getAppID())
                .setMchid(WXPayVo.getMchID())
                .setDescription(desc)
                .setOut_trade_no(outTradeNo)
                .setNotify_url(StaticComponent.WXJSAPIPAY_NOTIFY_URL)
                .setAmount(new Amount().setTotal(total.intValue()).setCurrency("CNY"))
                .setPayer(new Payer().setOpenid(openid))
                .setScene_info(new SceneInfo().setPayer_client_ip(IpUtil.getIpAddr(request)));
        String jsonStr = JSONUtil.toJsonStr(wxPayBo);
        //创建post方式请求对象
        X509Certificate certificate = PayKit.getCertificate(FileUtil.download(privateFilePath));
        String httpPost = url_prex + url;
        String post = getToken("POST", HttpUrl.parse(url_prex + url), WXPayVo.getMchID(), certificate.getSerialNumber().toString(16), privateKeyFilePath, jsonStr);
        HttpResponse httpResponse = doPost(httpPost, post, null, jsonStr);
        return JSONObject.parseObject(httpResponse.body()).getString("prepay_id");
    }

    /**
     * 骑手端小程序
     * 微信V3版本支付下单
     * @param url 小程序支付请求接口路径
     * @param outTradeNo 订单号
     * @param total 订单支付金额
     * @param openid 客户的openId
     * @param desc 商
     * @param request
     * @return 返回预支付订单标识
     * @throws Exception
     */
    public static String V3MpPay(String url, String outTradeNo, BigDecimal total,String openid,String desc,HttpServletRequest request) throws Exception {
        log.info("金额是多少 [{}]",total);
        log.info("微信OPENID [{}]",openid);
        WxPayBo wxPayBo = new WxPayBo()
                .setAppid(WXPayVo.getCusMpID())
                .setMchid(WXPayVo.getMchID())
                .setDescription(desc)
                .setOut_trade_no(outTradeNo)
                .setNotify_url(StaticComponent.WXJSAPIPAY_NOTIFY_URL)
                .setAmount(new Amount().setTotal(total.intValue()).setCurrency("CNY"))
                .setPayer(new Payer().setOpenid(openid))
                .setScene_info(new SceneInfo().setPayer_client_ip(IpUtil.getIpAddr(request)));
        String jsonStr = JSONUtil.toJsonStr(wxPayBo);
        //创建post方式请求对象
        X509Certificate certificate = PayKit.getCertificate(FileUtil.download(privateFilePath));
        String httpPost = url_prex + url;
        String post = getToken("POST", HttpUrl.parse(url_prex + url), WXPayVo.getMchID(), certificate.getSerialNumber().toString(16), privateKeyFilePath, jsonStr);
        HttpResponse httpResponse = doPost(httpPost, post, null, jsonStr);
        log.error("得到的结果：{}",httpResponse.body());
        log.info("支付订单唯一标识 [{}]",JSONObject.parseObject(httpResponse.body()).getString("prepay_id"));
        return JSONObject.parseObject(httpResponse.body()).getString("prepay_id");
    }

    /**
     * 微信V3版本 后台门店钱包充值
     * @param url 小程序支付请求接口路径
     * @param outTradeNo 订单号
     * @param total 订单支付金额
     * @param desc 商
     * @param request
     * @return 返回预支付订单标识
     * @throws Exception
     */
    public static String V3Native(HttpServletRequest request,String url, String outTradeNo, BigDecimal total,String desc) throws Exception {
        log.info("金额是多少 [{}]",total);
        WxPayBo wxPayBo = new WxPayBo()
                .setAppid("wx622a1adff1efa32a")
                .setMchid(WXPayVo.getMchID())
                .setDescription(desc)
                .setOut_trade_no(outTradeNo)
                .setNotify_url(StaticComponent.WXJSAPIPAY_NOTIFY_URL)
                .setAmount(new Amount().setTotal(total.intValue()).setCurrency("CNY"))
                .setScene_info(new SceneInfo().setPayer_client_ip(IpUtil.getIpAddr(request)));
        String jsonStr = JSONUtil.toJsonStr(wxPayBo);
        //创建post方式请求对象
        X509Certificate certificate = PayKit.getCertificate(FileUtil.download(privateFilePath));
        String httpPost = url_prex + url;
        String post = getToken("POST", HttpUrl.parse(url_prex + url), WXPayVo.getMchID(), certificate.getSerialNumber().toString(16), privateKeyFilePath, jsonStr);
        HttpResponse httpResponse = doPost(httpPost, post, null, jsonStr);
        return JSONObject.parseObject(httpResponse.body()).getString("code_url");
    }

    /**
     * 微信V3版本 H5支付
     * @param url 小程序支付请求接口路径
     * @param outTradeNo 订单号
     * @param total 订单支付金额
     * @param desc 商
     * @param request
     * @return 返回预支付订单标识
     * @throws Exception
     */
    public static String V3H5(HttpServletRequest request,String url, String outTradeNo, BigDecimal total,String desc) throws Exception {
        log.info("H5支付金额是多少 [{}]",total);
        WxPayBo wxPayBo = new WxPayBo()
                .setAppid("wx622a1adff1efa32a")
                .setMchid(WXPayVo.getMchID())
                .setDescription(desc)
                .setOut_trade_no(outTradeNo)
                .setNotify_url(StaticComponent.WXJSAPIPAY_NOTIFY_URL)
                .setAmount(new Amount().setTotal(total.intValue()).setCurrency("CNY"))
                .setScene_info(new SceneInfo().setPayer_client_ip(IpUtil.getIpAddr(request)));
        String jsonStr = JSONUtil.toJsonStr(wxPayBo);
        //创建post方式请求对象
        X509Certificate certificate = PayKit.getCertificate(FileUtil.download(privateFilePath));
        String httpPost = url_prex + url;
        String post = getToken("POST", HttpUrl.parse(url_prex + url), WXPayVo.getMchID(), certificate.getSerialNumber().toString(16), privateKeyFilePath, jsonStr);
        HttpResponse httpResponse = doPost(httpPost, post, null, jsonStr);
        return JSONObject.parseObject(httpResponse.body()).getString("h5_url");
    }



    /**
     * 客户V3版本支付下单
     * @param url 小程序支付请求接口路径
     * @param outTradeNo 订单号
     * @param total 订单支付金额
     * @param desc 商品描述
     * @param request
     * @return 返回预支付订单标识
     * @throws Exception
     */
    public static String V3cusAppPay(String url, String outTradeNo, BigDecimal total,String desc,HttpServletRequest request) throws Exception {
        WxPayBo wxPayBo = new WxPayBo()
                .setAppid(WXPayVo.getCustAppID())
                .setMchid(WXPayVo.getMchID())
                .setDescription(desc)
                .setOut_trade_no(outTradeNo)
                .setNotify_url(StaticComponent.WXJSAPIPAY_NOTIFY_URL)
                .setAmount(new Amount().setTotal(total.intValue()).setCurrency("CNY"))
                .setScene_info(new SceneInfo().setPayer_client_ip(IpUtil.getIpAddr(request)));
        String jsonStr = JSONUtil.toJsonStr(wxPayBo);
        //创建post方式请求对象
        X509Certificate certificate = PayKit.getCertificate(FileUtil.download(privateFilePath));
        String httpPost = url_prex + url;
        String post = getToken("POST", HttpUrl.parse(url_prex + url), WXPayVo.getMchID(), certificate.getSerialNumber().toString(16), privateKeyFilePath, jsonStr);
        HttpResponse httpResponse = doPost(httpPost, post, null, jsonStr);
        return JSONObject.parseObject(httpResponse.body()).getString("prepay_id");
    }


    /**
     * 骑手V3版本支付下单
     * @param url 小程序支付请求接口路径
     * @param outTradeNo 订单号
     * @param total 订单支付金额
     * @param desc 商品描述
     * @param request
     * @return 返回预支付订单标识
     * @throws Exception
     */
    public static String V3couAppPay(String url, String outTradeNo, BigDecimal total,String desc,HttpServletRequest request){
        WxPayBo wxPayBo = new WxPayBo()
                .setAppid(WXPayVo.getCourAppID())
                .setMchid(WXPayVo.getMchID())
                .setDescription(desc)
                .setOut_trade_no(outTradeNo)
                .setNotify_url(StaticComponent.WXJSAPIPAY_NOTIFY_URL)
                .setAmount(new Amount().setTotal(total.intValue()).setCurrency("CNY"))
                .setScene_info(new SceneInfo().setPayer_client_ip(IpUtil.getIpAddr(request)));
        String jsonStr = JSONUtil.toJsonStr(wxPayBo);
        //创建post方式请求对象
        X509Certificate certificate = PayKit.getCertificate(FileUtil.download(privateFilePath));
        String httpPost = url_prex + url;
        String post = null;
        try {
            post = getToken("POST", HttpUrl.parse(url_prex + url), WXPayVo.getMchID(), certificate.getSerialNumber().toString(16), privateKeyFilePath, jsonStr);
        } catch (Exception e) {
            log.error("骑手V3版本支付下单,outTradeNo={}",outTradeNo);
            e.printStackTrace();
        }
        HttpResponse httpResponse = doPost(httpPost, post, null, jsonStr);

        return JSONObject.parseObject(httpResponse.body()).getString("prepay_id");
    }


    /**
     * 微信V3版本支付下单
     * @param url
     * @param payChannelNum
     * @param total
     * @param amountRefund
     * @param desc
     * @return
     * @throws Exception
     */
    /*public static String V3wxRefund(String url, String payChannelNum, BigDecimal total,BigDecimal amountRefund,String desc) throws Exception {
        log.info("金额是多少 [{}]",total);
         Refund refund = new Refund()
                .setTransaction_id(payChannelNum)
                .setOut_refund_no(NumberUtils.get("T"))
                .setReason(desc)
                .setAmountRefund(new AmountRefund().setRefund(amountRefund.intValue()).setTotal(total.intValue()).setCurrency("CNY"));
        String jsonStr = JSONObject.toJSONString(refund);
        //创建post方式请求对象
        String httpPost = url_prex + url;
        HttpResponse httpResponse = doPost(httpPost, null, null, jsonStr);
        return JSONObject.parseObject(httpResponse.body()).toString();
    }*/


    /**
     * @param url           请求url
     * @param authorization 授权信息
     * @param serialNumber  公钥证书序列号
     * @param jsonData      请求参数
     * @return {@link HttpResponse} 请求返回的结果
     */
    private static HttpResponse doPost(String url, String authorization, String serialNumber, String jsonData) {
        return HttpRequest.post(url)
                .addHeaders(getHeaders(authorization, serialNumber))
                .body(jsonData)
                .execute();
    }

    /**
     * 构建HTTP请求头
     * @param authorization
     * @return
     */
    private static Map<String, String> getBaseHeaders(String authorization) {
        Map<String, String> headers = new HashMap<>(3);
        headers.put("Accept", ContentType.JSON.toString());
        headers.put("Authorization", authorization);
        return headers;
    }
    /**
     * 构建HTTP请求头
     * @param authorization
     * @return
     */
    private static Map<String, String> getHeaders(String authorization, String serialNumber) {
        Map<String, String> headers = getBaseHeaders(authorization);
        headers.put("Content-Type", ContentType.JSON.toString());
        if (StrUtil.isNotEmpty(serialNumber)) {
            headers.put("Wechatpay-Serial", serialNumber);
        }
        return headers;
    }


    /**
     * 客户小程序调起支付
     * @param prepayId 预支付交易会话标识
     * @return
     * @throws Exception
     */
    public static JSONObject WxTuneUp(String prepayId) throws Exception {
        String time = System.currentTimeMillis() / 1000 + "";
        String nonceStr = UUID.randomUUID().toString().replace("-", "");
        String packageStr = "prepay_id=" + prepayId;
        ArrayList<String> list = new ArrayList<>();
        list.add(WXPayVo.getAppID());//小程序id
        list.add(time);//时间戳
        list.add(nonceStr);//随机字符串
        list.add(packageStr);//预支付交易会话ID
        //加载签名
        String packageSign = sign(buildSignMessage(list), privateKeyFilePath);
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("appid", WXPayVo.getAppID());
        jsonObject.put("timeStamp", time);
        jsonObject.put("nonceStr", nonceStr);
        jsonObject.put("packages", packageStr);
        jsonObject.put("signType", "RSA");
        jsonObject.put("paySign", packageSign);
        return jsonObject;
    }

    /**
     * 骑手小程序调起支付
     * @param prepayId 预支付交易会话标识
     * @return
     * @throws Exception
     */
    public static JSONObject WxCusLet(String prepayId) throws Exception {
        log.info("############################prepay_id:{}",prepayId);
        String time = System.currentTimeMillis() / 1000 + "";
        String nonceStr = UUID.randomUUID().toString().replace("-", "");
        String packageStr = "prepay_id=" + prepayId;
        ArrayList<String> list = new ArrayList<>();
        list.add(WXPayVo.getCusMpID());//小程序id
        list.add(time);//时间戳
        list.add(nonceStr);//随机字符串
        list.add(packageStr);//预支付交易会话ID
        //加载签名
        String packageSign = sign(buildSignMessage(list), privateKeyFilePath);
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("appid", WXPayVo.getCusMpID());
        jsonObject.put("timeStamp", time);
        jsonObject.put("nonceStr", nonceStr);
        jsonObject.put("package", packageStr);
        jsonObject.put("signType", "RSA");
        jsonObject.put("paySign", packageSign);
        return jsonObject;
    }

    /**
     * 客户微信APP调起支付
     * @param prepayId 预支付交易会话标识
     * @return
     * @throws Exception
     */
    public static JSONObject WxAppTuneUp(String prepayId) throws Exception {
        String time = System.currentTimeMillis() / 1000 + "";
        String nonceStr = UUID.randomUUID().toString().replace("-", "");
        ArrayList<String> list = new ArrayList<>();
        list.add(WXPayVo.getCustAppID());//应用id
        list.add(time);//时间戳
        list.add(nonceStr);//随机字符串
        list.add(prepayId);//预支付交易会话ID
        //加载签名
        String packageSign = sign(buildSignMessage(list), privateKeyFilePath);
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("appid", WXPayVo.getCustAppID());
        jsonObject.put("partnerid",WXPayVo.getMchID());
        jsonObject.put("prepayid",prepayId);
        jsonObject.put("package","Sign=WXPay");
        jsonObject.put("timestamp", time);
        jsonObject.put("noncestr", nonceStr);
        jsonObject.put("sign", packageSign);
        return jsonObject;
    }

    /**
     * 派送员App调起支付
     * @param prepayId
     * @return
     * @throws Exception
     */
    public static JSONObject couAppTuneUp(String prepayId) {
        String time = System.currentTimeMillis() / 1000 + "";
        String nonceStr = UUID.randomUUID().toString().replace("-", "");
        ArrayList<String> list = new ArrayList<>();
        list.add(WXPayVo.getCourAppID());//应用id
        list.add(time);//时间戳
        list.add(nonceStr);//随机字符串
        list.add(prepayId);//预支付交易会话ID
        //加载签名
        String packageSign = null;
        try {
            packageSign = sign(buildSignMessage(list), privateKeyFilePath);
        } catch (Exception e) {
            log.error("派送员App调起支付出现异常,prepayId={}",prepayId);
            e.printStackTrace();
        }
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("appid", WXPayVo.getCourAppID());
        jsonObject.put("partnerid",WXPayVo.getMchID());
        jsonObject.put("prepayid",prepayId);
        jsonObject.put("package","Sign=WXPay");
        jsonObject.put("timestamp", time);
        jsonObject.put("noncestr", nonceStr);
        jsonObject.put("sign", packageSign);
        return jsonObject;
    }


    /**
     * 生成组装请求头
     *
     * @param method             请求方式
     * @param url                请求地址
     * @param mercId             商户ID
     * @param serialNo          证书序列号
     * @param privateKeyFilePath 私钥路径
     * @param body               请求体
     * @return 组装请求的数据
     * @throws Exception
     */
    public static String getToken(String method, HttpUrl url, String mercId, String serialNo, String privateKeyFilePath, String body) throws Exception {
        String nonceStr = UUID.randomUUID().toString().replace("-", "");
        long timestamp = System.currentTimeMillis()/1000;
        // 获取商户私钥
        String message = buildMessage(method, url,String.valueOf(timestamp), nonceStr, body);
        // 生成签名
        String signature = sign(message, privateKeyFilePath);
        boolean ss = WxPayV3Util.verifySignature(signature,String.valueOf(timestamp),nonceStr,body);
        Map<String, String> params = new HashMap<>(5);
        params.put("mchid", mercId);
        params.put("serial_no", serialNo);
        params.put("nonce_str", nonceStr);
        params.put("timestamp", String.valueOf(timestamp));
        params.put("signature", signature);
        return authType.concat(" ").concat(createLinkString(params, ",", false, true));
    }


    /**
     * 生成签名
     *
     * @param message            请求体
     * @param privateKeyFilePath 私钥的路径
     * @return 生成base64位签名信息
     * @throws Exception
     */
    static String sign(String message, String privateKeyFilePath) throws Exception {
        Signature sign = Signature.getInstance("SHA256withRSA");
        sign.initSign(getPrivateKey(privateKeyFilePath));
        sign.update(message.getBytes(StandardCharsets.UTF_8));
        return Base64.getEncoder().encodeToString(sign.sign());
    }

    public static boolean verifySignature(String signature, String body, String nonce, String timestamp) throws Exception {
        String buildSignMessage = PayKit.buildSignMessage(timestamp, nonce, body);
        // 获取证书
        X509Certificate certificate = PayKit.getCertificate(FileUtil.download(privateFilePath));
        PublicKey publicKey = certificate.getPublicKey();
        return RsaKit.checkByPublicKey(buildSignMessage, signature, publicKey);
    }

    /**
     * 构造签名串
     * @param method     GET,POST,PUT等
     * @param url       请求接口 /v3/certificates
     * @param timestamp 获取发起请求时的系统当前时间戳
     * @param nonceStr  随机字符串
     * @param body      请求报文主体
     * @return 待签名字符串
     */
    static String buildMessage(String method, HttpUrl url, String timestamp, String nonceStr,String body) {
        String canonicalUrl = url.encodedPath();
        if (url.encodedQuery() != null) {
            canonicalUrl += "?" + url.encodedQuery();
        }
        return new StringBuilder()
                .append(method).append("\n")
                .append(canonicalUrl).append("\n")
                .append(timestamp).append("\n")
                .append(nonceStr).append("\n")
                .append(body).append("\n").toString();
    }


    /**
     * 获取商户私钥。
     * @param filename 私钥文件路径  (required)
     * @return 私钥对象
     */
    static PrivateKey getPrivateKey(String filename) throws IOException {
        InputStream certData = FileUtil.download(filename);
        byte[] bytes = new byte[0];
        bytes = new byte[certData.available()];
        certData.read(bytes);
        String content = new String(bytes, "UTF-8");
        try {
            String privateKey = content.replace("-----BEGIN PRIVATE KEY-----", "")
                    .replace("-----END PRIVATE KEY-----", "")
                    .replaceAll("\\s+", "");
            KeyFactory kf = KeyFactory.getInstance("RSA");
            return kf.generatePrivate(new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey)));
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("当前Java环境不支持RSA", e);
        } catch (InvalidKeySpecException e) {
            throw new RuntimeException("无效的密钥格式");
        }
    }

    /**
     * 构造签名串
     * @param signMessage 待签名的参数
     * @return 构造后带待签名串
     */
    static String buildSignMessage(ArrayList<String> signMessage) {
        if (signMessage == null || signMessage.size() <= 0) {
            return null;
        }
        StringBuilder sbf = new StringBuilder();
        for (String str : signMessage) {
            sbf.append(str).append("\n");
        }
        return sbf.toString();
    }

    /**
     * 处理微信异步回调
     * @param request
     * @param response
     * @param
     */
    public static JSONObject notify(HttpServletRequest request, HttpServletResponse response) throws Exception {
        Map<String, String> map = new HashMap<>(12);
        String result = readData(request);
        // 需要通过证书序列号查找对应的证书，verifyNotify 中有验证证书的序列号
        String plainText = verifyNotify(result, ApiV3Key);
        if (StrUtil.isNotEmpty(plainText)) {
            response.setStatus(200);
            map.put("code", "SUCCESS");
            map.put("message", "SUCCESS");
        } else {
            response.setStatus(500);
            map.put("code", "ERROR");
            map.put("message", "签名错误");
        }
        response.setHeader("Content-type", ContentType.JSON.toString());
        response.getOutputStream().write(JSONUtil.toJsonStr(map).getBytes(StandardCharsets.UTF_8));
        response.flushBuffer();
        return JSONObject.parseObject(plainText);
    }

    /**
     * v3 支付异步通知验证签名
     * @param body 异步通知密文
     * @param key  api 密钥
     * @return 异步通知明文
     * @throws Exception 异常信息
     */
    static String verifyNotify(String body, String key) throws Exception {
        // 获取平台证书序列号
        cn.hutool.json.JSONObject resultObject = JSONUtil.parseObj(body);
        cn.hutool.json.JSONObject resource = resultObject.getJSONObject("resource");
        String cipherText = resource.getStr("ciphertext");
        String nonceStr = resource.getStr("nonce");
        String associatedData = resource.getStr("associated_data");
        AesUtil aesUtil = new AesUtil(key.getBytes(StandardCharsets.UTF_8));
        // 密文解密
        return aesUtil.decryptToString(
                associatedData.getBytes(StandardCharsets.UTF_8),
                nonceStr.getBytes(StandardCharsets.UTF_8),
                cipherText
        );
    }

    static String readData(HttpServletRequest request) {
        BufferedReader br = null;
        try {
            StringBuilder result = new StringBuilder();
            br = request.getReader();
            for (String line; (line = br.readLine()) != null; ) {
                if (result.length() > 0) {
                    result.append("\n");
                }
                result.append(line);
            }
            return result.toString();
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            if (br != null) {
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 参数拼接
     * @param params
     * @param connStr
     * @param encode
     * @param quotes
     * @return
     */
    public static String createLinkString(Map<String, String> params, String connStr, boolean encode, boolean quotes) {
        List<String> keys = new ArrayList<String>(params.keySet());
        Collections.sort(keys);
        StringBuilder content = new StringBuilder();
        for (int i = 0; i < keys.size(); i++) {
            String key = keys.get(i);
            String value = params.get(key);
            // 拼接时，不包括最后一个&字符
            if (i == keys.size() - 1) {
                if (quotes) {
                    content.append(key).append("=").append('"').append(encode ? urlEncode(value) : value).append('"');
                } else {
                    content.append(key).append("=").append(encode ? urlEncode(value) : value);
                }
            } else {
                if (quotes) {
                    content.append(key).append("=").append('"').append(encode ? urlEncode(value) : value).append('"').append(connStr);
                } else {
                    content.append(key).append("=").append(encode ? urlEncode(value) : value).append(connStr);
                }
            }
        }
        return content.toString();
    }
}