diff --git a/src/main/java/io/v/nutz/pay/bank/china/BankChinaWxPayService.java b/src/main/java/io/v/nutz/pay/bank/china/BankChinaWxPayService.java index 4b6a936..df54811 100644 --- a/src/main/java/io/v/nutz/pay/bank/china/BankChinaWxPayService.java +++ b/src/main/java/io/v/nutz/pay/bank/china/BankChinaWxPayService.java @@ -238,6 +238,7 @@ public class BankChinaWxPayService { String wcPayDataSignType = XmlUtil.getNodeText(document, "wcPayDataSignType"); // 签名 String wcPayDataPaySign = XmlUtil.getNodeText(document, "wcPayDataPaySign"); + String qrCode = XmlUtil.getNodeText(document, "qrCode"); return new WxAppPayResponse().setOrderSeq(orderSeq) .setTranStatus(tranStatus) .setTradeType(tradeType) @@ -251,7 +252,7 @@ public class BankChinaWxPayService { .setWcPayDataNonceStr(wcPayDataNonceStr) .setWcPayDataPackage(wcPayDataPackage) .setWcPayDataSignType(wcPayDataSignType) - .setWcPayDataPaySign(wcPayDataPaySign); + .setWcPayDataPaySign(wcPayDataPaySign).setQrCode(qrCode); } private void checkConfigPayProperties() { diff --git a/src/main/java/io/v/nutz/pay/bank/china/controller/BankNotifyController.java b/src/main/java/io/v/nutz/pay/bank/china/controller/BankNotifyController.java index 29e832d..8a81572 100644 --- a/src/main/java/io/v/nutz/pay/bank/china/controller/BankNotifyController.java +++ b/src/main/java/io/v/nutz/pay/bank/china/controller/BankNotifyController.java @@ -2,24 +2,25 @@ package io.v.nutz.pay.bank.china.controller; import io.v.nutz.pay.annotations.Api; import io.v.nutz.pay.annotations.ApiOperation; +import io.v.nutz.pay.bank.china.property.ChinaPayProperties; +import io.v.nutz.pay.bank.china.sign.BankPaySign; import io.v.nutz.pay.cache.ChinaBankOrderWaitPayCacheService; import io.v.nutz.pay.record.service.IMemberPayRecordService; +import io.v.nutz.pay.util.XmlUtil; import lombok.extern.slf4j.Slf4j; import org.nutz.integration.jedis.RedisService; import org.nutz.ioc.loader.annotation.Inject; import org.nutz.ioc.loader.annotation.IocBean; import org.nutz.mvc.annotation.At; import org.nutz.mvc.annotation.Ok; +import org.w3c.dom.Document; import javax.servlet.http.HttpServletRequest; -/** - * 支付通知 controller - */ +@Slf4j @IocBean @Api("支付通知") @At("/notify") -@Slf4j public class BankNotifyController { @@ -34,6 +35,9 @@ public class BankNotifyController @Inject private ChinaBankOrderWaitPayCacheService chinaBankOrderWaitPayCacheService; + @Inject + private ChinaPayProperties chinaPayProperties; + @Ok("json") @At("/china/bank/pay") @@ -45,6 +49,42 @@ public class BankNotifyController String signature = request.getParameter("signature"); log.info("[message]=[" + message + "]"); log.info("[signature]=[" + signature + "]"); + + + try + { + String plainText = new String(io.v.nutz.pay.util.Base64.decode(message), "UTF-8"); + log.debug("[base64解码后message]=[" + plainText + "]"); + + chinaPayProperties.load(); + boolean isValid = BankPaySign.getInstance().verifySignature( + chinaPayProperties.getVerifyCerFile(), + signature, + plainText + ); + + if(!isValid) { + log.error("[VERIFY FAILED] 中国银行支付通知验签失败"); + throw new RuntimeException("签名验证失败"); + } + + log.info("[VERIFY OK] 中国银行支付通知验签成功"); + + Document document = XmlUtil.createDocument(plainText); + String responseCode = XmlUtil.getNodeText(document, "responseCode"); + if("OK".equals(responseCode)){ + String orderNo = XmlUtil.getNodeText(document, "orderNo"); + String tranStatus = XmlUtil.getNodeText(document, "tranStatus"); + String payCatalog = XmlUtil.getNodeText(document, "payCatalog"); + log.debug("[orderNo]=["+orderNo+"] [tranStatus]=["+ tranStatus+"] [payCatalog]=["+payCatalog+"]"); + request.setAttribute("result",responseCode+",Notice Success!"); + } + } + catch (Exception e){ + log.error("中国银行支付通知处理异常:{}", e.getMessage()); + e.printStackTrace(); + } + } diff --git a/src/main/java/io/v/nutz/pay/bank/china/pojo/WxAppPayResponse.java b/src/main/java/io/v/nutz/pay/bank/china/pojo/WxAppPayResponse.java index 2faeee6..b05e1f6 100644 --- a/src/main/java/io/v/nutz/pay/bank/china/pojo/WxAppPayResponse.java +++ b/src/main/java/io/v/nutz/pay/bank/china/pojo/WxAppPayResponse.java @@ -14,6 +14,9 @@ public class WxAppPayResponse implements Serializable { // 银行订单流水号 银行的订单流水号(银行产生的订单唯一标识) private String orderSeq; + // 订单号 + private String orderNo; + // 下单状态 1:成功 2:失败 private String tranStatus; @@ -52,4 +55,7 @@ public class WxAppPayResponse implements Serializable { // 签名 private String wcPayDataPaySign; + + // 二维码 + private String qrCode; } diff --git a/src/main/java/io/v/nutz/pay/bank/china/sign/BankPaySign.java b/src/main/java/io/v/nutz/pay/bank/china/sign/BankPaySign.java index 961044f..867ccbd 100644 --- a/src/main/java/io/v/nutz/pay/bank/china/sign/BankPaySign.java +++ b/src/main/java/io/v/nutz/pay/bank/china/sign/BankPaySign.java @@ -4,9 +4,14 @@ import cn.hutool.core.util.StrUtil; import io.v.nutz.pay.util.PKCS7Tool; import lombok.extern.slf4j.Slf4j; -/** - * 中国银行支付签名 - */ +import java.io.FileInputStream; +import java.io.InputStream; +import java.security.KeyStore; +import java.security.PublicKey; +import java.security.Signature; +import java.security.cert.Certificate; +import java.util.Enumeration; + @Slf4j public class BankPaySign { @@ -15,14 +20,6 @@ public class BankPaySign { return new BankPaySign(); } - /** - * 生成签名数据 - * @param signKeyStorePath 证书库路径 - * @param signKeyStorePassword 证书库口令 - * @param signKeyPassword 签名私钥口令,与证书库口令相同 - * @param businessParams 业务参数 参数格式 param|param|param - * @return signData - */ public String getSinData(String signKeyStorePath, String signKeyStorePassword, String signKeyPassword, String businessParams) { if(StrUtil.isBlank(signKeyStorePath)) { throw new RuntimeException("缺少证书库路径"); @@ -43,12 +40,59 @@ public class BankPaySign { byte[] businessDataBytes = businessParams.getBytes("UTF-8"); String signData = tool.signature(signKeyStorePath, signKeyPassword, businessDataBytes); log.info("签名数据:signData-{}", signData); -// Boolean signDataBoolean = tool.verifySignature(signKeyStorePath, signKeyPassword, businessDataBytes, Base64.getDecoder().decode(signData)); -// log.info("签名数据生成状态: signDataBoolean-{}", signDataBoolean); return signData; } catch (Exception e) { e.printStackTrace(); throw new RuntimeException(e.getMessage()); } } + + public boolean verifySignature(String verifyCerFilePath, String signature, String plainText) { + if(StrUtil.isBlank(verifyCerFilePath)) { + throw new RuntimeException("缺少验签证书文件路径"); + } + if(StrUtil.isBlank(signature)) { + throw new RuntimeException("缺少签名数据"); + } + if(plainText == null) { + plainText = ""; + } + log.info("验签信息:verifyCerFilePath-{}, signature-{}, plainText-{}", verifyCerFilePath, signature, plainText); + + try { + byte[] plainTextBytes = plainText.getBytes("UTF-8"); + byte[] signatureBytes = io.v.nutz.pay.util.Base64.decode(signature); + + InputStream fis4cer = new FileInputStream(verifyCerFilePath); + KeyStore keyStore = KeyStore.getInstance("PKCS12"); + keyStore.load(fis4cer, null); + fis4cer.close(); + + Enumeration aliases = keyStore.aliases(); + String alias = null; + while (aliases.hasMoreElements()) { + alias = aliases.nextElement(); + } + + Certificate cert = keyStore.getCertificate(alias); + PublicKey publicKey = cert.getPublicKey(); + + Signature sig = Signature.getInstance("SHA256withRSA"); + sig.initVerify(publicKey); + sig.update(plainTextBytes); + boolean isValid = sig.verify(signatureBytes); + + if(isValid) { + log.info("[VERIFY OK] 中国银行支付验签成功"); + } else { + log.error("[VERIFY FAILED] 中国银行支付验签失败"); + } + + return isValid; + } catch (Exception e) { + e.printStackTrace(); + log.error("中国银行支付验签异常:{}", e.getMessage()); + throw new RuntimeException("验签失败: " + e.getMessage()); + } + } } diff --git a/src/main/java/io/v/nutz/pay/record/module/MemberPayRecord.java b/src/main/java/io/v/nutz/pay/record/module/MemberPayRecord.java index 565a9c5..934a7a6 100644 --- a/src/main/java/io/v/nutz/pay/record/module/MemberPayRecord.java +++ b/src/main/java/io/v/nutz/pay/record/module/MemberPayRecord.java @@ -113,4 +113,9 @@ public class MemberPayRecord implements Serializable { @Column(value = "device_ip") @ColDefine(type = ColType.VARCHAR, width = 100) private String deviceIp; + + @Comment("设备IP") + @Column(value = "qr_code") + @ColDefine(type = ColType.VARCHAR, width = 100) + private String qrCode; } diff --git a/src/main/java/io/v/nutz/pay/record/service/impl/MemberPayRecordServiceImpl.java b/src/main/java/io/v/nutz/pay/record/service/impl/MemberPayRecordServiceImpl.java index 1e7db9c..d8f1710 100644 --- a/src/main/java/io/v/nutz/pay/record/service/impl/MemberPayRecordServiceImpl.java +++ b/src/main/java/io/v/nutz/pay/record/service/impl/MemberPayRecordServiceImpl.java @@ -6,6 +6,7 @@ import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import io.v.nutz.base.service.impl.BaseServiceImpl; import io.v.nutz.pay.bank.china.BankChinaWxPayService; +import io.v.nutz.pay.bank.china.pojo.WxAppPayResponse; import io.v.nutz.pay.bank.china.pojo.WxPayOrderQueryResponse; import io.v.nutz.pay.bank.china.property.ChinaPayProperties; import io.v.nutz.pay.cache.ChinaBankOrderPayNotifyCacheService; @@ -20,7 +21,11 @@ import io.v.nutz.pay.order.service.IMemberPaymentOrderService; import io.v.nutz.pay.record.module.MemberPayRecord; import io.v.nutz.pay.record.service.IMemberPayRecordService; import io.v.nutz.pay.util.BillNoUtil; +import io.v.nutz.zhgh.memberCost.model.MemberPaymentInfo; +import io.v.nutz.zhgh.memberCost.service.MemberPaymentService; +import io.v.nutz.zhgh.memberCost.service.impl.MemberPaymentServiceImpl; import lombok.extern.slf4j.Slf4j; +import org.nutz.dao.Cnd; import org.nutz.dao.Dao; import org.nutz.dao.Sqls; import org.nutz.dao.sql.Sql; @@ -31,7 +36,8 @@ import java.util.List; @Slf4j @IocBean(args = {"refer:dao"}) -public class MemberPayRecordServiceImpl extends BaseServiceImpl implements IMemberPayRecordService { +public class MemberPayRecordServiceImpl extends BaseServiceImpl implements IMemberPayRecordService +{ @Inject private ChinaPayProperties chinaPayProperties; @@ -42,12 +48,16 @@ public class MemberPayRecordServiceImpl extends BaseServiceImpl @Inject private BankChinaWxPayService bankChinaWxPayService; + + @Inject + private MemberPaymentService memberPaymentService; @Inject private ChinaBankOrderWaitPayCacheService chinaBankOrderWaitPayCacheService; @Inject private ChinaBankOrderPayNotifyCacheService chinaBankOrderPayNotifyCacheService; + public MemberPayRecordServiceImpl(Dao dao) { super(dao); } @@ -57,9 +67,12 @@ public class MemberPayRecordServiceImpl extends BaseServiceImpl Sql sql = Sqls.create("select * from member_pay_record where pay_code = @pay_code limit 0,1"); sql.setParam("pay_code", code); List records = this.listEntity(sql); - if(records != null && records.size() > 0) { + if (records != null && records.size() > 0) + { return records.get(0); - } else { + } + else + { return null; } } @@ -69,9 +82,12 @@ public class MemberPayRecordServiceImpl extends BaseServiceImpl Sql sql = Sqls.create("select * from member_pay_record where id = @id limit 0,1"); sql.params().set("id", id); List records = this.listEntity(sql); - if(records != null && records.size() > 0) { + if (records != null && records.size() > 0) + { return records.get(0); - } else { + } + else + { return null; } } @@ -81,9 +97,12 @@ public class MemberPayRecordServiceImpl extends BaseServiceImpl Sql sql = Sqls.create("select * from member_pay_record where goods_trade_code = @goods_trade_code limit 0,1"); sql.params().set("goods_trade_code", goodsTradeCode); List records = this.listEntity(sql); - if(records != null && records.size() > 0) { + if (records != null && records.size() > 0) + { return records.get(0); - } else { + } + else + { return null; } } @@ -92,7 +111,8 @@ public class MemberPayRecordServiceImpl extends BaseServiceImpl public List getByGoodsTradeCode(Object... goodsTradeOrders) { Sql sql = Sqls.create("select * from member_pay_record where $goodsTradeOrders"); StringBuilder where = new StringBuilder(); - for(int i = 0; i < goodsTradeOrders.length; i++) { + for (int i = 0; i < goodsTradeOrders.length; i++) + { where.append("goods_trade_code='" + goodsTradeOrders[i].toString() + "' or "); } String whereStr = where.toString().substring(0, where.toString().lastIndexOf("or")); @@ -117,25 +137,34 @@ public class MemberPayRecordServiceImpl extends BaseServiceImpl record.setDeviceIp(deviceIp); // 中国银行支付中商品交易单号需要特殊处理 该单号用于发起支付-> 商户订单号 - if(PayChannelEnum.CHINA_BANK.getCode().equals(payChannel)) { + if (PayChannelEnum.CHINA_BANK.getCode().equals(payChannel)) + { record.setGoodsTradeCode(chinaPayProperties.getMerchantNoAfterChar(6).concat(BillNoUtil.getTimeNo(24))); - } else { + } + else + { record.setGoodsTradeCode(record.getPayCode()); } // 会员工会缴费 - if(MemberPayBusinessTypeEnum.UNION_PAYMENT.getCode().equals(businessType)) { - // 会员工会缴费订单信息 - MemberPaymentOrder paymentOrder = memberPaymentOrderService.getById(businessId); - if(paymentOrder == null) { - throw new RuntimeException("未获取到会员工会缴费订单信息"); - } - record.setBusinessOrderCode(paymentOrder.getOrderCode()); - record.setPayMoney(paymentOrder.getMoney()); - record.setMemberId(paymentOrder.getMemberId()); - record.setPayRemark("工会会员缴费-订单:" + paymentOrder.getOrderCode()); + if (MemberPayBusinessTypeEnum.UNION_PAYMENT.getCode().equals(businessType)) + { - } else { + List paymentUserList = memberPaymentService.query(Cnd.where("id", "=", businessId)); + if (paymentUserList == null || paymentUserList.isEmpty()) + { + throw new RuntimeException("缴费记录未找到"); + } + MemberPaymentInfo memberPaymentInfo = paymentUserList.get(0); + + record.setBusinessOrderCode(memberPaymentInfo.getTradeNo()); + record.setPayMoney(Double.valueOf(memberPaymentInfo.getPaymentMoney())); + record.setMemberId(memberPaymentInfo.getUserId()); + record.setPayRemark("工会会员缴费-订单:" + memberPaymentInfo.getLoginname()); + + } + else + { throw new RuntimeException("不支持的支付业务"); } this.insert(record); @@ -145,7 +174,8 @@ public class MemberPayRecordServiceImpl extends BaseServiceImpl @Override public void paySuccess(String goodsTradeCode, Double oriPayAmount) { MemberPayRecord record = this.getByGoodsTradeCode(goodsTradeCode); - if(record == null) { + if (record == null) + { throw new RuntimeException("商品交易单号不存在"); } record.setStatus(MemberPayStatusEnum.SUCCESS.getCode()); @@ -153,10 +183,13 @@ public class MemberPayRecordServiceImpl extends BaseServiceImpl this.updateById(record); // 会员工会缴费 - if(MemberPayBusinessTypeEnum.UNION_PAYMENT.getCode().equals(record.getBusinessType())) { + if (MemberPayBusinessTypeEnum.UNION_PAYMENT.getCode().equals(record.getBusinessType())) + { // 支付成功 memberPaymentOrderService.paySuccess(record.getBusinessId(), oriPayAmount); - } else { + } + else + { throw new RuntimeException("不支持的业务支付通知"); } } @@ -164,7 +197,8 @@ public class MemberPayRecordServiceImpl extends BaseServiceImpl @Override public void payFail(String goodsTradeCode, String failReason) { MemberPayRecord record = this.getByGoodsTradeCode(goodsTradeCode); - if(record == null) { + if (record == null) + { throw new RuntimeException("商品交易单号不存在"); } record.setStatus(MemberPayStatusEnum.FAIL.getCode()); @@ -173,73 +207,93 @@ public class MemberPayRecordServiceImpl extends BaseServiceImpl this.updateById(record); // 会员工会缴费 - if(MemberPayBusinessTypeEnum.UNION_PAYMENT.getCode().equals(record.getBusinessType())) { + if (MemberPayBusinessTypeEnum.UNION_PAYMENT.getCode().equals(record.getBusinessType())) + { // 支付成功 memberPaymentOrderService.payFail(record.getBusinessId()); - } else { + } + else + { throw new RuntimeException("不支持的业务支付通知"); } } @Override public void checkChinaPayOrderStatus() { - try { + try + { // 初始化配置参数 chinaPayProperties.load(); String goodsTradeOrder = chinaBankOrderWaitPayCacheService.get(); - if (StrUtil.isBlank(goodsTradeOrder)) { + if (StrUtil.isBlank(goodsTradeOrder)) + { log.info("未检测中国银行支付订单:null"); return; } List goodsTradeOrders = JSON.parseArray(goodsTradeOrder, String.class); log.info("检测中国银行支付订单:{}", goodsTradeOrders.size()); - if (goodsTradeOrders.size() == 0) { + if (goodsTradeOrders.size() == 0) + { return; } List payRecords = this.getByGoodsTradeCode(goodsTradeOrders.toArray()); - if (payRecords == null || payRecords.isEmpty()) { + if (payRecords == null || payRecords.isEmpty()) + { return; } - for (MemberPayRecord record : payRecords) { - if (!record.getStatus().equals(MemberPayStatusEnum.WAIT.getCode())) { + for (MemberPayRecord record : payRecords) + { + if (!record.getStatus().equals(MemberPayStatusEnum.WAIT.getCode())) + { continue; } - if (!record.getPayChannel().equals(PayChannelEnum.CHINA_BANK.getCode())) { + if (!record.getPayChannel().equals(PayChannelEnum.CHINA_BANK.getCode())) + { continue; } WxPayOrderQueryResponse response = bankChinaWxPayService.queryOrder(record.getGoodsTradeCode()); - if(chinaBankOrderPayNotifyCacheService.exists(record.getGoodsTradeCode())) { + if (chinaBankOrderPayNotifyCacheService.exists(record.getGoodsTradeCode())) + { continue; } chinaBankOrderPayNotifyCacheService.setex(record.getGoodsTradeCode(), 30, record.getGoodsTradeCode()); - if(response.getTranStatus() != null - &&(response.getTranStatus().equals(CommonConstants.ONE.toString()) || response.getTranStatus().equals(CommonConstants.TWO.toString()))) { + if (response.getTranStatus() != null + && (response.getTranStatus().equals(CommonConstants.ONE.toString()) || response.getTranStatus().equals(CommonConstants.TWO.toString()))) + { // 支付成功 - if(response.getTranStatus().equals(CommonConstants.ONE.toString())) { + if (response.getTranStatus().equals(CommonConstants.ONE.toString())) + { this.paySuccess(response.getOrderNo(), Double.valueOf(response.getTranAmount())); - // 支付失败 - } else { + // 支付失败 + } + else + { this.payFail(response.getOrderNo(), "支付失败"); } chinaBankOrderWaitPayCacheService.delItem(record.getGoodsTradeCode()); } } - } catch (Exception e) { + } + catch (Exception e) + { e.printStackTrace(); log.error("中国银行支付检测待支付订单异常:{}", e.getMessage()); } } private MemberPayRecord sendPay(MemberPayRecord record) { - try { - Object requestResult = null; + try + { + WxAppPayResponse requestResult = null; // 中国银行支付渠道 - if (PayChannelEnum.CHINA_BANK.getCode().equals(record.getPayChannel())) { + if (PayChannelEnum.CHINA_BANK.getCode().equals(record.getPayChannel())) + { // 微信APP支付 - if (ChannelTypeEnum.WX_APP.getCode().equals(record.getChannelType())) { + if (ChannelTypeEnum.WX_APP.getCode().equals(record.getChannelType())) + { // 获取中国银行微信支付拉起支付订单信息 requestResult = bankChinaWxPayService.appPay( record.getGoodsTradeCode(), @@ -251,14 +305,21 @@ public class MemberPayRecordServiceImpl extends BaseServiceImpl record.getDeviceIp()); } - } else { + } + else + { throw new RuntimeException("不支持的支付方式"); } - if (requestResult != null) { + if (requestResult != null) + { record.setRequestResult(JSONObject.toJSONString(requestResult)); + record.setBusinessOrderCode(requestResult.getOrderSeq()); + record.setQrCode(requestResult.getQrCode()); this.updateById(record); } - } catch (Exception e) { + } + catch (Exception e) + { this.payFail(record.getGoodsTradeCode(), e.getMessage()); throw new RuntimeException(e.getMessage()); } diff --git a/src/main/java/io/v/nutz/pay/util/Base64.java b/src/main/java/io/v/nutz/pay/util/Base64.java new file mode 100644 index 0000000..b1a6da8 --- /dev/null +++ b/src/main/java/io/v/nutz/pay/util/Base64.java @@ -0,0 +1,13 @@ +package io.v.nutz.pay.util; + +public final class Base64 { + private static final Base64Coder coder = new Base64Coder(); + + public static String encode(byte abyte0[]) { + return coder.encode(abyte0); + } + + public static byte[] decode(String s) { + return coder.decode(s); + } +} \ No newline at end of file diff --git a/src/main/java/io/v/nutz/pay/util/Base64Coder.java b/src/main/java/io/v/nutz/pay/util/Base64Coder.java new file mode 100644 index 0000000..17197ef --- /dev/null +++ b/src/main/java/io/v/nutz/pay/util/Base64Coder.java @@ -0,0 +1,198 @@ +package io.v.nutz.pay.util; + +/** + * + * @description + * + * @author + * + * @modified_by + */ +public final class Base64Coder { + + protected static boolean isWhiteSpace(char c) { + return c == ' ' || c == '\r' || c == '\n' || c == '\t'; + } + + protected static boolean isPad(char c) { + return c == '='; + } + + protected static boolean isData(char c) { + return c < '\200' && base64Alphabet[c] != -1; + } + + protected static boolean isBase64(char c) { + return isWhiteSpace(c) || isPad(c) || isData(c); + } + + public String encode(byte abyte0[]) { + if (abyte0 == null) + return null; + int i = abyte0.length * 8; + if (i == 0) + return ""; + int j = i % 24; + int k = i / 24; + int l = j == 0 ? k : k + 1; + char ac[] = null; + ac = new char[l * 4]; + int i1 = 0; + int j1 = 0; + for (int k1 = 0; k1 < k; k1++) { + byte byte5 = abyte0[j1++]; + byte byte8 = abyte0[j1++]; + byte byte10 = abyte0[j1++]; + byte byte3 = (byte) (byte8 & 15); + byte byte0 = (byte) (byte5 & 3); + byte byte11 = (byte5 & -128) != 0 ? (byte) (byte5 >> 2 ^ 192) : (byte) (byte5 >> 2); + byte byte14 = (byte8 & -128) != 0 ? (byte) (byte8 >> 4 ^ 240) : (byte) (byte8 >> 4); + byte byte16 = (byte10 & -128) != 0 ? (byte) (byte10 >> 6 ^ 252) : (byte) (byte10 >> 6); + ac[i1++] = lookUpBase64Alphabet[byte11]; + ac[i1++] = lookUpBase64Alphabet[byte14 | byte0 << 4]; + ac[i1++] = lookUpBase64Alphabet[byte3 << 2 | byte16]; + ac[i1++] = lookUpBase64Alphabet[byte10 & 63]; + } + + if (j == 8) { + byte byte6 = abyte0[j1]; + byte byte1 = (byte) (byte6 & 3); + byte byte12 = (byte6 & -128) != 0 ? (byte) (byte6 >> 2 ^ 192) : (byte) (byte6 >> 2); + ac[i1++] = lookUpBase64Alphabet[byte12]; + ac[i1++] = lookUpBase64Alphabet[byte1 << 4]; + ac[i1++] = '='; + ac[i1++] = '='; + } else if (j == 16) { + byte byte7 = abyte0[j1]; + byte byte9 = abyte0[j1 + 1]; + byte byte4 = (byte) (byte9 & 15); + byte byte2 = (byte) (byte7 & 3); + byte byte13 = (byte7 & -128) != 0 ? (byte) (byte7 >> 2 ^ 192) : (byte) (byte7 >> 2); + byte byte15 = (byte9 & -128) != 0 ? (byte) (byte9 >> 4 ^ 240) : (byte) (byte9 >> 4); + ac[i1++] = lookUpBase64Alphabet[byte13]; + ac[i1++] = lookUpBase64Alphabet[byte15 | byte2 << 4]; + ac[i1++] = lookUpBase64Alphabet[byte4 << 2]; + ac[i1++] = '='; + } + return new String(ac); + } + + public byte[] decode(String s) { + if (s == null) + return null; + char ac[] = s.toCharArray(); + int i = removeWhiteSpace(ac); + if (i % 4 != 0) + return null; + int j = i / 4; + if (j == 0) + return new byte[0]; + byte abyte0[] = null; + byte byte0 = 0; + byte byte1 = 0; + char c = '\0'; + char c1 = '\0'; + char c2 = '\0'; + char c3 = '\0'; + int k = 0; + int l = 0; + int i1 = 0; + abyte0 = new byte[j * 3]; + for (; k < j - 1; k++) { + if (!isData(c = ac[i1++]) || !isData(c1 = ac[i1++]) || !isData(c2 = ac[i1++]) || !isData(c3 = ac[i1++])) + return null; + byte0 = base64Alphabet[c]; + byte1 = base64Alphabet[c1]; + byte byte2 = base64Alphabet[c2]; + byte byte5 = base64Alphabet[c3]; + abyte0[l++] = (byte) (byte0 << 2 | byte1 >> 4); + abyte0[l++] = (byte) ((byte1 & 15) << 4 | byte2 >> 2 & 15); + abyte0[l++] = (byte) (byte2 << 6 | byte5); + } + + if (!isData(c = ac[i1++]) || !isData(c1 = ac[i1++])) + return null; + byte0 = base64Alphabet[c]; + byte1 = base64Alphabet[c1]; + c2 = ac[i1++]; + c3 = ac[i1++]; + if (!isData(c2) || !isData(c3)) { + if (isPad(c2) && isPad(c3)) { + if ((byte1 & 15) != 0) + return null; + byte abyte1[] = new byte[k * 3 + 1]; + System.arraycopy(abyte0, 0, abyte1, 0, k * 3); + abyte1[l] = (byte) (byte0 << 2 | byte1 >> 4); + return abyte1; + } + if (isPad(c2) && isPad(c3)) + return null; + byte byte3 = base64Alphabet[c2]; + if ((byte3 & 3) != 0) + return null; + byte abyte2[] = new byte[k * 3 + 2]; + System.arraycopy(abyte0, 0, abyte2, 0, k * 3); + abyte2[l++] = (byte) (byte0 << 2 | byte1 >> 4); + abyte2[l] = (byte) ((byte1 & 15) << 4 | byte3 >> 2 & 15); + return abyte2; + } + byte byte4 = base64Alphabet[c2]; + byte byte6 = base64Alphabet[c3]; + abyte0[l++] = (byte) (byte0 << 2 | byte1 >> 4); + abyte0[l++] = (byte) ((byte1 & 15) << 4 | byte4 >> 2 & 15); + abyte0[l++] = (byte) (byte4 << 6 | byte6); + return abyte0; + } + + protected static int removeWhiteSpace(char ac[]) { + if (ac == null) + return 0; + int i = 0; + int j = ac.length; + for (int k = 0; k < j; k++) + if (!isWhiteSpace(ac[k])) + ac[i++] = ac[k]; + + return i; + } + + private static final byte base64Alphabet[]; + + private static final char lookUpBase64Alphabet[]; + + static { + base64Alphabet = new byte[128]; + lookUpBase64Alphabet = new char[64]; + for (int i = 0; i < 128; i++) + base64Alphabet[i] = -1; + + for (int j = 90; j >= 65; j--) + base64Alphabet[j] = (byte) (j - 65); + + for (int k = 122; k >= 97; k--) + base64Alphabet[k] = (byte) ((k - 97) + 26); + + for (int l = 57; l >= 48; l--) + base64Alphabet[l] = (byte) ((l - 48) + 52); + + base64Alphabet[43] = 62; + base64Alphabet[47] = 63; + for (int i1 = 0; i1 <= 25; i1++) + lookUpBase64Alphabet[i1] = (char) (65 + i1); + + int j1 = 26; + for (int k1 = 0; j1 <= 51; k1++) { + lookUpBase64Alphabet[j1] = (char) (97 + k1); + j1++; + } + + int l1 = 52; + for (int i2 = 0; l1 <= 61; i2++) { + lookUpBase64Alphabet[l1] = (char) (48 + i2); + l1++; + } + + lookUpBase64Alphabet[62] = '+'; + lookUpBase64Alphabet[63] = '/'; + } +} diff --git a/src/main/java/io/v/nutz/pay/util/XmlUtil.java b/src/main/java/io/v/nutz/pay/util/XmlUtil.java index af996e9..4b58a30 100644 --- a/src/main/java/io/v/nutz/pay/util/XmlUtil.java +++ b/src/main/java/io/v/nutz/pay/util/XmlUtil.java @@ -1,8 +1,11 @@ package io.v.nutz.pay.util; -import java.io.*; -import java.util.ArrayList; -import java.util.List; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.InputSource; + import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.transform.Transformer; @@ -11,9 +14,15 @@ import javax.xml.transform.dom.DOMSource; import javax.xml.transform.sax.SAXSource; import javax.xml.transform.stream.StreamResult; import javax.xml.transform.stream.StreamSource; -import javax.xml.validation.*; -import org.w3c.dom.*; -import org.xml.sax.InputSource; +import javax.xml.validation.Schema; +import javax.xml.validation.SchemaFactory; +import javax.xml.validation.Validator; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.InputStreamReader; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.List; public class XmlUtil { @@ -64,6 +73,9 @@ public class XmlUtil public static Document createDocument(String s) throws Exception { + // 预处理XML,修复未转义的&符号 + s = fixUnescapedAmpersands(s); + DocumentBuilder documentbuilder = createDocumentBuilder(); return documentbuilder.parse(new ByteArrayInputStream(s.getBytes("UTF-8"))); } @@ -71,6 +83,9 @@ public class XmlUtil public static Document createDocument(String s, String s1) throws Exception { + // 预处理XML,修复未转义的&符号 + s = fixUnescapedAmpersands(s); + DocumentBuilder documentbuilder = createDocumentBuilder(); return documentbuilder.parse(new ByteArrayInputStream(s.getBytes(s1))); } @@ -203,4 +218,69 @@ public class XmlUtil transformer.transform(domsource, new StreamResult(stringwriter)); return stringwriter.toString(); } + + private static String fixUnescapedAmpersands(String xml) { + if (xml == null || xml.isEmpty()) { + return xml; + } + + StringBuilder result = new StringBuilder(); + int i = 0; + int len = xml.length(); + + while (i < len) { + char c = xml.charAt(i); + + // 如果找到&符号 + if (c == '&') { + // 检查是否是已经正确转义的实体(如 & < &#等) + if (i + 1 < len) { + int j = i + 1; + // 查找分号 + boolean foundSemicolon = false; + boolean isValidEntity = false; + + while (j < len && j < i + 10) { // 实体引用通常不会太长 + if (xml.charAt(j) == ';') { + foundSemicolon = true; + // 检查&和;之间的内容是否是合法的实体名或数值引用 + String entityContent = xml.substring(i + 1, j); + if (entityContent.startsWith("#") || + entityContent.equals("amp") || + entityContent.equals("lt") || + entityContent.equals("gt") || + entityContent.equals("quot") || + entityContent.equals("apos")) { + isValidEntity = true; + } + break; + } else if (xml.charAt(j) == '&' || xml.charAt(j) == '<' || xml.charAt(j) == '>') { + // 遇到其他特殊字符,说明这不是一个有效的实体引用 + break; + } + j++; + } + + // 如果是合法的实体引用,保持原样 + if (foundSemicolon && isValidEntity) { + result.append(c); + i++; + } else { + // 否则将&替换为& + result.append("&"); + i++; + } + } else { + // &是最后一个字符,替换为& + result.append("&"); + i++; + } + } else { + result.append(c); + i++; + } + } + + return result.toString(); + } } diff --git a/src/main/resources/views/mobile/memberCost/memberCostList.html b/src/main/resources/views/mobile/memberCost/memberCostList.html index b334d57..84d2b63 100644 --- a/src/main/resources/views/mobile/memberCost/memberCostList.html +++ b/src/main/resources/views/mobile/memberCost/memberCostList.html @@ -157,16 +157,16 @@ layout("/mobile/platform.html"){ - - - - + + + + + + + + + +