diff --git a/doc/lib/command.bat b/doc/lib/command.bat index 2aaf8b3..6f58cf1 100644 --- a/doc/lib/command.bat +++ b/doc/lib/command.bat @@ -3,4 +3,6 @@ echo off :: 添加jar包到本地仓库 cmd /k "mvn install:install-file -Dfile=jacob-1.21.jar -DgroupId=com.jacob -DartifactId=jacob -Dversion=1.21 -Dpackaging=jar" +:: cmd /k "mvn install:install-file -Dfile=hsaf-common-1.0.0-SNAPSHOT.jar -DgroupId=hsaf.common -DartifactId=hsaf -Dversion=1.0.0-SNAPSHOT -Dpackaging=jar" + pause diff --git a/doc/lib/hsaf-common-1.0.0-SNAPSHOT.jar b/doc/lib/hsaf-common-1.0.0-SNAPSHOT.jar new file mode 100644 index 0000000..86e908b Binary files /dev/null and b/doc/lib/hsaf-common-1.0.0-SNAPSHOT.jar differ diff --git a/pom.xml b/pom.xml index c2d3919..a1adb8c 100644 --- a/pom.xml +++ b/pom.xml @@ -179,6 +179,13 @@ itext-asian 5.2.0 + + + diff --git a/src/main/java/com/dpkj/common/config/YnChsConfig.java b/src/main/java/com/dpkj/common/config/YnChsConfig.java index 5a23c5b..e59e477 100644 --- a/src/main/java/com/dpkj/common/config/YnChsConfig.java +++ b/src/main/java/com/dpkj/common/config/YnChsConfig.java @@ -20,7 +20,7 @@ public class YnChsConfig { private String url; /** - * 医保机构编码 + * 两定医保机构编码 */ private String fixmeDinsCode; /** @@ -43,5 +43,30 @@ public class YnChsConfig { */ private String cityCode; + /** + * 应用ID + */ + private String appid; + /** + * 应用密钥 + */ + private String appKey; + /** + * 应用名称 + */ + private String appName; + /** + * 渠道私钥 + */ + private String channelPrivateKey; + /** + * 渠道公钥 + */ + private String channelPublicKey; + /** + * 平台公钥 + */ + private String platformPublicKey; + } diff --git a/src/main/java/com/dpkj/common/constant/CharsetConst.java b/src/main/java/com/dpkj/common/constant/CharsetConst.java index a4a1a35..5603a26 100644 --- a/src/main/java/com/dpkj/common/constant/CharsetConst.java +++ b/src/main/java/com/dpkj/common/constant/CharsetConst.java @@ -9,6 +9,7 @@ public interface CharsetConst { // 中文编码 String GB2312 = "GB2312"; - + // UTF8 + String UTF8 = "UTF-8"; } diff --git a/src/main/java/com/dpkj/modules/chs/hischs/controller/HispayController.java b/src/main/java/com/dpkj/modules/chs/hischs/controller/HispayController.java index 9693464..0108800 100644 --- a/src/main/java/com/dpkj/modules/chs/hischs/controller/HispayController.java +++ b/src/main/java/com/dpkj/modules/chs/hischs/controller/HispayController.java @@ -29,17 +29,16 @@ public class HispayController { private final IHispayService hispayService; - /** * 通过医保卡或医保电子凭证读卡 * * @return com.dpkj.common.vo.Result * @author 萧道子 2025/5/20 */ - @PostMapping("findReadCode") - public Result findReadCode() { + @PostMapping("getUserInfoByChsCode") + public Result getUserInfoByChsCode() { try { - JSONObject res = hispayService.readCode(); + JSONObject res = hispayService.getUserInfoByChsCode(); return Result.ok("成功", res); } catch (Exception e) { e.printStackTrace(); @@ -56,15 +55,15 @@ public class HispayController { * @return com.dpkj.common.vo.Result * @author 萧道子 2025/5/28 */ - @PostMapping("findReadCard") - public Result findReadCard(@RequestBody JSONObject data) { + @PostMapping("getUserInfoByChsCard") + public Result getUserInfoByChsCard(@RequestBody JSONObject data) { try { String password = data.getString("password"); if (StrUtil.isEmpty(password)) { throw new RuntimeException("密码不可为空"); } - JSONObject res = hispayService.readCard(password); + JSONObject res = hispayService.getUserInfoByChsCard(password); return Result.ok("成功", res); } catch (Exception e) { e.printStackTrace(); diff --git a/src/main/java/com/dpkj/modules/chs/hischs/service/IHispayService.java b/src/main/java/com/dpkj/modules/chs/hischs/service/IHispayService.java index 26678bc..8940f3f 100644 --- a/src/main/java/com/dpkj/modules/chs/hischs/service/IHispayService.java +++ b/src/main/java/com/dpkj/modules/chs/hischs/service/IHispayService.java @@ -8,21 +8,20 @@ import com.dpkj.modules.chs.hischs.vo.OutpatientFinalModel; public interface IHispayService { /** - * 通过医保电子凭证读卡 + * 通过医保电子凭证获-取患者身份信息 * * @return com.alibaba.fastjson.JSONObject * @author 萧道子 2025/5/28 */ - JSONObject readCode(); - + JSONObject getUserInfoByChsCode(); /** - * 通过医保卡-读卡 + * 通过医保卡-取患者身份信息 * * @return com.alibaba.fastjson.JSONObject * @author 萧道子 2025/5/28 */ - JSONObject readCard(String password); + JSONObject getUserInfoByChsCard(String password); /** diff --git a/src/main/java/com/dpkj/modules/chs/hischs/service/impl/HispayServiceImpl.java b/src/main/java/com/dpkj/modules/chs/hischs/service/impl/HispayServiceImpl.java index 2c77493..2127db4 100644 --- a/src/main/java/com/dpkj/modules/chs/hischs/service/impl/HispayServiceImpl.java +++ b/src/main/java/com/dpkj/modules/chs/hischs/service/impl/HispayServiceImpl.java @@ -15,7 +15,6 @@ import com.jacob.com.Dispatch; import com.jacob.com.Variant; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; -import org.w3c.dom.Document; import javax.annotation.PostConstruct; import java.util.Map; @@ -69,6 +68,150 @@ public class HispayServiceImpl implements IHispayService { // ComThread.Release(); //容易导致线程发生阻塞 } + + @Override + public JSONObject getUserInfoByChsCode() { + /** 1、组装参数 */ + JSONObject params = new JSONObject() + .fluentPut("cardtype", "9") // 1.就诊卡,2.医保卡,5.门诊号,6.患者姓名和电话号码,8.电子健康码/卡,9.医保电子凭证 + .fluentPut("cardno", "") // 患者就诊卡号 + .fluentPut("sfzh", "") // 身份证号 + .fluentPut("hzxm", "") // 患者姓名 + .fluentPut("phone", ""); // 患者电话号码 + String paramsXml = this.handleParams(params); + + /** 2、调用COM函数 */ + log.debug("[HispayServiceImpl][readCode][医保读卡-电子凭证] 接口入参:{}", paramsXml); + Variant vres = new Variant("", true); + Variant call = Dispatch.call(dispatch, "fRun", "BMZXX010", paramsXml, vres); + String resStr = vres.getStringRef(); + log.debug("[HispayServiceImpl][readCode][医保读卡-电子凭证] call返回值:{} 结果:{}", call, resStr); + + + /** 3、处理读卡结果 */ + JSONObject result = verifyResult(resStr); + if (!result.containsKey("item")) { + throw new RuntimeException("item数据为空"); + } + + return result.getJSONObject("item"); + } + + @Override + public JSONObject getUserInfoByChsCard(String password) { + /** 1、组装参数 */ + JSONObject params = new JSONObject() + .fluentPut("cardtype", "2") // 1.就诊卡,2.医保卡,5.门诊号,6.患者姓名和电话号码,8.电子健康码/卡,9.医保电子凭证 + .fluentPut("cardno", "") // 患者就诊卡号 + .fluentPut("sfzh", "") // 身份证号 + .fluentPut("hzxm", "") // 患者姓名 + .fluentPut("phone", ""); // 患者电话号码 + String paramsXml = this.handleParams(params, password); + + /** 2、调用COM函数 */ + log.debug("[HispayServiceImpl][readCard][医保读卡-医保卡] 接口入参:{}", paramsXml); + Variant vres = new Variant("", true); + Variant call = Dispatch.call(dispatch, "fRun", "BMZXX010", paramsXml, vres); + String resStr = vres.getStringRef(); + log.debug("[HispayServiceImpl][readCard][医保读卡-医保卡] call返回值:{} 结果:{}", call, resStr); + + /** 3、处理读卡结果 */ + JSONObject result = this.verifyResult(resStr); + if (!result.containsKey("item")) { + throw new RuntimeException("item数据为空"); + } + + return result.getJSONObject("item"); + } + + + @Override + public ResultData chsCodeAsOutpatientBegin(OutpatientBeginModel data) { + // 用户读卡-生成token + JSONObject userInfo = this.getUserInfoByChsCode(); + String patientId = userInfo.getString("patid"); + data.setPatientId(patientId); + + // 更新常量状态 已读卡 + ChsPayStateConst.put(data.getPrescriptionNo(), 1); + + return this.outpatientBudget(data); + } + + @Override + public ResultData chsCodeAsOutpatientFinal(OutpatientFinalModel data) { + /** 1、组装参数 */ + String requestTime = DateUtil.now(); + data.setPaytime(requestTime); + JSONObject params = ((JSONObject) JSON.toJSON(data)) + .fluentPut("czksfbz", "0") // 是否扣院内账户,与预算保持一致 + .fluentPut("zfjsbz", "0") // 是否自费结算,与预算保持一致,0根据病人医保代码结算,1自费结算 + .fluentPut("ybrc", "") + .fluentPut("ptlsh", "") + .fluentPut("jysm", ""); + String paramsXml = this.handleParams(params); + + /** 2、调用COM函数 */ + log.debug("[HispayServiceImpl][chsCodeAsOutpatientFinal][门诊缴费-结算] 接口入参:{}", paramsXml); + Variant resVariant = new Variant("", true); + Variant call = Dispatch.call(dispatch, "fRun", "BMZJF002", paramsXml, resVariant); + String resStr = resVariant.getStringRef(); + log.debug("[HispayServiceImpl][chsCodeAsOutpatientFinal][门诊缴费-结算] call返回值:{} 结果:{}", call, resStr); + String responseTime = DateUtil.now(); + + /** 3、处理结果 */ + JSONObject result = verifyResult(resStr); + + return new ResultData() + .setRequestTime(requestTime) + .setRequestContent(paramsXml) + .setResponseTime(responseTime) + .setPatientId(data.getPatid()) + .setResponseContent(resStr) + .setResult(result); + } + + + /** + * 医保预结算 + * + * @param data : + * @return com.dpkj.common.vo.ResultData + * @author 萧道子 2025/10/25 + */ + private ResultData outpatientBudget(OutpatientBeginModel data) { + /** 1、组装参数 */ + JSONObject params = new JSONObject() + .fluentPut("patid", data.getPatientId()) // 病人ID + .fluentPut("cfxhhj", data.getPrescriptionNo()) // 划价单据,格式:单据1,单据2,单据3 + .fluentPut("czksfbz", "0") // 是否扣院内账户,0不使用院内账户,1使用院内账户(默认不使用院内账户) + .fluentPut("zfjsbz", "0") // 是否自费结算,0根据医保代码缴费,1自费结算(默认自费结算) + .fluentPut("ybrc", ""); // 医保入参,xml节点 + String paramsXml = this.handleParams(params); + + String requestTime = DateUtil.now(); + + /** 2、调用COM函数 */ + log.debug("[HispayServiceImpl][outpatientBudget][门诊缴费-预算] 接口入参:{}", paramsXml); + Variant resVariant = new Variant("", true); + Variant call = Dispatch.call(dispatch, "fRun", "BMZJF001", paramsXml, resVariant); + String resStr = resVariant.getStringRef(); + log.debug("[HispayServiceImpl][outpatientBudget][门诊缴费-预算] call返回值:{} 结果:{}", call, resStr); + + String responseTime = DateUtil.now(); + + /** 3、处理结果 */ + JSONObject result = verifyResult(resStr); + + return new ResultData() + .setRequestTime(requestTime) + .setRequestContent(paramsXml) + .setResponseTime(responseTime) + .setPatientId(data.getPatientId()) + .setResponseContent(resStr) + .setResult(result); + } + /** * 处理参数 * @@ -77,7 +220,7 @@ public class HispayServiceImpl implements IHispayService { * @return java.lang.String * @author 萧道子 2025/5/27 */ - private String processParameters(JSONObject params, String password) { + private String handleParams(JSONObject params, String password) { JSONObject req = new JSONObject() {{ put("timestamp", ""); // 请求发送时间 DateUtil.format(DateUtil.date(), "yyyyMMddHHmmss") put("requestid", ""); // 唯一请求id IDGenerator.getSnowflakeIdToStr() @@ -85,8 +228,11 @@ public class HispayServiceImpl implements IHispayService { put("password", password); // 密码 put("params", params); }}; - Document document = XmlUtil.mapToXml(req, "request"); - return XmlUtil.toStr(document, "UTF-8", false, true); + return this.JsonObjectToXml(req); + } + + private String handleParams(JSONObject params) { + return handleParams(params, null); } @@ -98,7 +244,6 @@ public class HispayServiceImpl implements IHispayService { * @author 萧道子 2025/5/28 */ private JSONObject verifyResult(String resStr) { - if (StrUtil.isBlank(resStr)) { throw new RuntimeException("信息获取失败"); } @@ -119,149 +264,15 @@ public class HispayServiceImpl implements IHispayService { } - @Override - public JSONObject readCode() { - /** 1、组装参数 */ - JSONObject val = new JSONObject() - .fluentPut("cardtype", "9") // 1.就诊卡,2.医保卡,5.门诊号,6.患者姓名和电话号码,8.电子健康码/卡,9.医保电子凭证 - .fluentPut("cardno", "") // 患者就诊卡号 - .fluentPut("sfzh", "") // 身份证号 - .fluentPut("hzxm", "") // 患者姓名 - .fluentPut("phone", ""); // 患者电话号码 - String params = processParameters(val, null); - log.debug("[HispayServiceImpl][readCode][医保读卡-电子凭证] 接口入参:{}", params); - - /** 2、调用COM函数 */ - Variant vres = new Variant("", true); - Variant call = Dispatch.call(dispatch, "fRun", "BMZXX010", params, vres); - - String resStr = vres.getStringRef(); - log.debug("[HispayServiceImpl][readCode][医保读卡-电子凭证] call返回值:{} 结果:{}", call, resStr); - - - /** 3、处理读卡结果 */ - JSONObject result = verifyResult(resStr); - if (!result.containsKey("item")) { - throw new RuntimeException("item数据为空"); - } - - return result.getJSONObject("item"); + /** + * 将jsonobject转为xml + * + * @param val : + * @return java.lang.String + * @author 萧道子 2025/10/25 + */ + private String JsonObjectToXml(JSONObject val) { + return XmlUtil.toStr(XmlUtil.mapToXml(val, "request"), "UTF-8", false, true); } - @Override - public JSONObject readCard(String password) { - - /** 1、组装参数 */ - JSONObject val = new JSONObject() - .fluentPut("cardtype", "2") // 1.就诊卡,2.医保卡,5.门诊号,6.患者姓名和电话号码,8.电子健康码/卡,9.医保电子凭证 - .fluentPut("cardno", "") // 患者就诊卡号 - .fluentPut("sfzh", "") // 身份证号 - .fluentPut("hzxm", "") // 患者姓名 - .fluentPut("phone", ""); // 患者电话号码 - String params = processParameters(val, password); - log.debug("[HispayServiceImpl][readCard][医保读卡-医保卡] 接口入参:{}", params); - - /** 2、调用COM函数 */ - Variant vres = new Variant("", true); - Variant call = Dispatch.call(dispatch, "fRun", "BMZXX010", params, vres); - - String resStr = vres.getStringRef(); - log.debug("[HispayServiceImpl][readCard][医保读卡-医保卡] call返回值:{} 结果:{}", call, resStr); - - /** 3、处理读卡结果 */ - JSONObject result = verifyResult(resStr); - if (!result.containsKey("item")) { - throw new RuntimeException("item数据为空"); - } - - return result.getJSONObject("item"); - } - - - private ResultData outpatientBudget(OutpatientBeginModel data) { - - /** 1、组装参数 */ - JSONObject val = new JSONObject() - .fluentPut("patid", data.getPatientId()) // 病人ID - .fluentPut("cfxhhj", data.getPrescriptionNo()) // 划价单据,格式:单据1,单据2,单据3 - .fluentPut("czksfbz", "0") // 是否扣院内账户,0不使用院内账户,1使用院内账户(默认不使用院内账户) - .fluentPut("zfjsbz", "0") // 是否自费结算,0根据医保代码缴费,1自费结算(默认自费结算) - .fluentPut("ybrc", ""); // 医保入参,xml节点 - String params = processParameters(val, data.getPassword()); - log.debug("[HispayServiceImpl][outpatientBudget][门诊缴费-预算] 接口入参:{}", params); - - String requestTime = DateUtil.now(); - - /** 2、调用COM函数 */ - Variant resVariant = new Variant("", true); - Variant call = Dispatch.call(dispatch, "fRun", "BMZJF001", params, resVariant); - - String responseTime = DateUtil.now(); - String resStr = resVariant.getStringRef(); - log.debug("[HispayServiceImpl][outpatientBudget][门诊缴费-预算] call返回值:{} 结果:{}", call, resStr); - - - /** 3、处理结果 */ - JSONObject result = verifyResult(resStr); - - return new ResultData() - .setRequestTime(requestTime) - .setRequestContent(params) - .setResponseTime(responseTime) - .setPatientId(data.getPatientId()) - .setResponseContent(resStr) - .setResult(result); - } - - - @Override - public ResultData chsCodeAsOutpatientBegin(OutpatientBeginModel data) { - // 用户读卡-生成token - JSONObject userInfo = readCode(); - String patientId = userInfo.getString("patid"); - data.setPatientId(patientId); - - // 更新常量状态 已读卡 - ChsPayStateConst.put(data.getPrescriptionNo(), 1); - - return outpatientBudget(data); - } - - - @Override - public ResultData chsCodeAsOutpatientFinal(OutpatientFinalModel data) { - - /** 1、组装参数 */ - String requestTime = DateUtil.now(); - data.setPaytime(requestTime); - JSONObject val = ((JSONObject) JSON.toJSON(data)) - .fluentPut("czksfbz", "0") // 是否扣院内账户,与预算保持一致 - .fluentPut("zfjsbz", "0") // 是否自费结算,与预算保持一致,0根据病人医保代码结算,1自费结算 - .fluentPut("ybrc", "") - .fluentPut("ptlsh", "") - .fluentPut("jysm", ""); - String params = processParameters(val, null); - log.debug("[HispayServiceImpl][chsCodeAsOutpatientFinal][门诊缴费-结算] 接口入参:{}", params); - - /** 2、调用COM函数 */ - Variant resVariant = new Variant("", true); - Variant call = Dispatch.call(dispatch, "fRun", "BMZJF002", params, resVariant); - - String responseTime = DateUtil.now(); - String resStr = resVariant.getStringRef(); - log.debug("[HispayServiceImpl][chsCodeAsOutpatientFinal][门诊缴费-结算] call返回值:{} 结果:{}", call, resStr); - - /** 3、处理结果 */ - JSONObject result = verifyResult(resStr); - - return new ResultData() - .setRequestTime(requestTime) - .setRequestContent(params) - .setResponseTime(responseTime) - .setPatientId(data.getPatid()) - .setResponseContent(resStr) - .setResult(result); - } - - } diff --git a/src/main/java/com/dpkj/modules/chs/ynchs/constant/YnchsConst.java b/src/main/java/com/dpkj/modules/chs/ynchs/constant/YnchsConst.java new file mode 100644 index 0000000..cbf8834 --- /dev/null +++ b/src/main/java/com/dpkj/modules/chs/ynchs/constant/YnchsConst.java @@ -0,0 +1,38 @@ +package com.dpkj.modules.chs.ynchs.constant; + +public interface YnchsConst { + + String URL_BASE = "/eapdomain/callService"; + + /** + * 人员信息获取 + */ + String URL_1101 = URL_BASE; + + /** + * 身份认证 + */ + String URL_1191 = URL_BASE; + + /** + * 移动支付-费用明细上传 + */ + String URL_6201 = "/org/local/api/hos/uldFeeInfo"; + + /** + * 移动支付-支付下单 + */ + String URL_6202 = "/org/local/api/hos/pay_order"; + + /** + * 移动支付-医保退费 + */ + String URL_6203 = "/org/local/api/hos/refund_Order"; + + /** + * 移动支付-医保订单信息同步 + */ + String URL_6204 = URL_BASE; + + +} diff --git a/src/main/java/com/dpkj/modules/chs/ynchs/controller/YnChsPayController.java b/src/main/java/com/dpkj/modules/chs/ynchs/controller/YnChsPayController.java index 007d0ce..44167c1 100644 --- a/src/main/java/com/dpkj/modules/chs/ynchs/controller/YnChsPayController.java +++ b/src/main/java/com/dpkj/modules/chs/ynchs/controller/YnChsPayController.java @@ -1,8 +1,9 @@ package com.dpkj.modules.chs.ynchs.controller; -import com.alibaba.fastjson.JSONObject; import com.dpkj.common.vo.Result; -import com.dpkj.modules.chs.ynchs.service.IYnChsPayService; +import com.dpkj.modules.chs.padchs.response.ChsCheckResponse; +import com.dpkj.modules.chs.ynchs.service.IYnchspayByDllService; +import com.dpkj.modules.chs.ynchs.service.IYnchspayByWebapiService; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.PostMapping; @@ -20,18 +21,21 @@ import org.springframework.web.bind.annotation.RestController; @RequestMapping("/chs/yn") public class YnChsPayController { - private final IYnChsPayService ynChsPayService; + private final IYnchspayByWebapiService ynchspayByWebapiService; + + private final IYnchspayByDllService ynchspayByDllService; + /** - * 获取支付凭证 + * 刷脸获取身份信息 * * @return com.dpkj.common.vo.Result - * @author 萧道子 2025/10/22 + * @author 萧道子 2025/10/24 */ - @PostMapping("getPayToken") - public Result getPayToken() { + @PostMapping("getUserByFace") + public Result getUserByFace() { try { - JSONObject result = ynChsPayService.getPayToken(); + ChsCheckResponse result = ynchspayByDllService.getUserByFace(); return Result.ok("成功", result); } catch (Exception e) { return Result.error(e.getMessage()); diff --git a/src/main/java/com/dpkj/modules/chs/ynchs/dll/YnChsPayDll.java b/src/main/java/com/dpkj/modules/chs/ynchs/dll/YnChsPayDll.java index b4e85cf..914249f 100644 --- a/src/main/java/com/dpkj/modules/chs/ynchs/dll/YnChsPayDll.java +++ b/src/main/java/com/dpkj/modules/chs/ynchs/dll/YnChsPayDll.java @@ -2,7 +2,6 @@ package com.dpkj.modules.chs.ynchs.dll; import com.sun.jna.Library; import com.sun.jna.Native; -import com.sun.jna.Pointer; import lombok.extern.slf4j.Slf4j; /** @@ -21,8 +20,8 @@ public class YnChsPayDll { */ public static EcDll instance() throws DllRegistrationException { try { - // return Native.load("CHSInterfaceYn.dll", EcDll.class); - return Native.load("D:\\Code\\CHS\\CHSInterfaceYn.dll", EcDll.class); + return Native.load("CHSInterfaceYn.dll", EcDll.class); + // return Native.load("D:\\Code\\CHS\\CHSInterfaceYn.dll", EcDll.class); } catch (UnsatisfiedLinkError e) { log.info("[YnChsPayDll][instance][云南省级医保动态库] SDK注册失败:{}", e.getMessage()); throw new DllRegistrationException("Failed to load YnChsPayDll library: ", e); @@ -62,7 +61,7 @@ public class YnChsPayDll { * @return int * @author 萧道子 2025/10/22 */ - int Init(String fixmeDinsCode, String infoSysCode, String infoSysSign, String url, Pointer errMsg); + int Init(String fixmeDinsCode, String infoSysCode, String infoSysSign, String url, Object errMsg); /** @@ -78,7 +77,7 @@ public class YnChsPayDll { * @return int * @author 萧道子 2025/10/22 */ - int BusinessHandle(String fixmeDinsCode, String infoSysCode, String infoSysSign, String inputData, Pointer outputData, Pointer errMsg); + int BusinessHandle(String fixmeDinsCode, String infoSysCode, String infoSysSign, String inputData, Object outputData, Object errMsg); /** @@ -94,7 +93,7 @@ public class YnChsPayDll { * @return int * @author 萧道子 2025/10/22 */ - int BusinessHandleW(String fixmeDinsCode, String infoSysCode, String infoSysSign, String inputData, Pointer outputData, Pointer errMsg); + int BusinessHandleW(String fixmeDinsCode, String infoSysCode, String infoSysSign, String inputData, Object outputData, Object errMsg); } diff --git a/src/main/java/com/dpkj/modules/chs/ynchs/service/IYnChsPayService.java b/src/main/java/com/dpkj/modules/chs/ynchs/service/IYnChsPayService.java deleted file mode 100644 index f6dabcd..0000000 --- a/src/main/java/com/dpkj/modules/chs/ynchs/service/IYnChsPayService.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.dpkj.modules.chs.ynchs.service; - -import com.alibaba.fastjson.JSONObject; - -/** - * @Auther: 萧道子 - * @Date: 2025/8/7 14:45 - * @Description: - */ -public interface IYnChsPayService { - - /** - * 下单 - * - * @param val : - * @return void - * @author 萧道子 2025/10/22 - */ - JSONObject placeOrderByApi(String val); - - - /** - * 下单 - * - * @param val : - * @return void - * @author 萧道子 2025/10/22 - */ - JSONObject getPayToken(); -} diff --git a/src/main/java/com/dpkj/modules/chs/ynchs/service/IYnchspayByDllService.java b/src/main/java/com/dpkj/modules/chs/ynchs/service/IYnchspayByDllService.java new file mode 100644 index 0000000..3cd102a --- /dev/null +++ b/src/main/java/com/dpkj/modules/chs/ynchs/service/IYnchspayByDllService.java @@ -0,0 +1,21 @@ +package com.dpkj.modules.chs.ynchs.service; + +import com.dpkj.modules.chs.padchs.response.ChsCheckResponse; + +/** + * @Auther: 萧道子 + * @Date: 2025/8/7 14:45 + * @Description: + */ +public interface IYnchspayByDllService { + + /** + * 通过扫脸获取用户信息 + * + * @return com.dpkj.modules.chs.padchs.response.ChsCheckResponse + * @author 萧道子 2025/10/24 + */ + ChsCheckResponse getUserByFace(); + + +} diff --git a/src/main/java/com/dpkj/modules/chs/ynchs/service/IYnchspayByWebapiService.java b/src/main/java/com/dpkj/modules/chs/ynchs/service/IYnchspayByWebapiService.java new file mode 100644 index 0000000..d96c3b9 --- /dev/null +++ b/src/main/java/com/dpkj/modules/chs/ynchs/service/IYnchspayByWebapiService.java @@ -0,0 +1,12 @@ +package com.dpkj.modules.chs.ynchs.service; + +/** + * @Auther: 萧道子 + * @Date: 2025/8/7 14:45 + * @Description: + */ +public interface IYnchspayByWebapiService { + + + +} diff --git a/src/main/java/com/dpkj/modules/chs/ynchs/service/impl/YnChsPayServiceImpl.java b/src/main/java/com/dpkj/modules/chs/ynchs/service/impl/YnChsPayServiceImpl.java deleted file mode 100644 index 28489f8..0000000 --- a/src/main/java/com/dpkj/modules/chs/ynchs/service/impl/YnChsPayServiceImpl.java +++ /dev/null @@ -1,185 +0,0 @@ -package com.dpkj.modules.chs.ynchs.service.impl; - -import cn.hutool.core.date.DatePattern; -import cn.hutool.core.date.DateTime; -import cn.hutool.core.date.DateUtil; -import cn.hutool.core.util.RandomUtil; -import cn.hutool.crypto.SecureUtil; -import cn.hutool.http.HttpRequest; -import cn.hutool.http.HttpResponse; -import com.alibaba.fastjson.JSONObject; -import com.alibaba.fastjson.serializer.SerializerFeature; -import com.dpkj.common.config.YnChsConfig; -import com.dpkj.common.constant.CharsetConst; -import com.dpkj.common.utils.IDGenerator; -import com.dpkj.modules.chs.padchs.response.ChsCheckResponse; -import com.dpkj.modules.chs.padchs.service.IPadChsPayService; -import com.dpkj.modules.chs.ynchs.dll.YnChsPayDll; -import com.dpkj.modules.chs.ynchs.service.IYnChsPayService; -import com.sun.jna.Memory; -import com.sun.jna.Pointer; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -import javax.annotation.PostConstruct; - -/** - * @Auther: 萧道子 - * @Date: 2025/8/7 14:46 - * @Description: - */ -@Slf4j -@Service -public class YnChsPayServiceImpl implements IYnChsPayService { - - private YnChsPayDll.EcDll dll; - - @Autowired - private YnChsConfig ynChsConfig; - @Autowired - private IPadChsPayService padChsPayService; - - @PostConstruct - public void postConstruct() { - log.info("[YnChsPayServiceImpl][postConstruct][云南省局医保DLL] 初始化动态链接库"); - try { - dll = YnChsPayDll.instance(); - } catch (YnChsPayDll.DllRegistrationException e) { - log.error("[YnChsPayServiceImpl][postConstruct][云南省局医保DLL] 加载失败:{}", e.getMessage()); - throw new RuntimeException("云南省局医保DLL库加载失败:" + e.getMessage(), e); - } - } - - - @Override - public JSONObject placeOrderByApi(String val) { - String nonce = IDGenerator.getSnowflakeIdToStr(); - DateTime date = DateUtil.date(); - String timestamp = DateUtil.format(date, DatePattern.PURE_DATETIME_PATTERN); - String infoSysSign = ynChsConfig.getInfoSysSign(); - String signature = SecureUtil.sha256(timestamp + infoSysSign + nonce); - - String fixmeDinsCode = ynChsConfig.getFixmeDinsCode(); - String cityCode = ynChsConfig.getCityCode(); - String fixmeDinsName = ynChsConfig.getFixmeDinsName(); - String msgid = fixmeDinsCode + timestamp + RandomUtil.randomNumbers(4); - - JSONObject inputData = new JSONObject().fluentPut("data", new JSONObject().fluentPut("mdtrt_cert_type", "02") - .fluentPut("mdtrt_cert_no", "02") - .fluentPut("card_sn", "") - .fluentPut("begntime", "") - .fluentPut("psn_cert_type", "01") - .fluentPut("certno", "530111199701031117") - .fluentPut("psn_name", "薛家俊")); - JSONObject params = new JSONObject() - .fluentPut("infno", "1101") // 交易编号 - .fluentPut("msgid", msgid) // 发送方报文ID 定点医药机构编号(12)+时间(14)+顺序号(4) 时间格式:yyyyMMddHHmmss - .fluentPut("mdtrtarea_admvs", cityCode) // 就医地医保区划 - .fluentPut("insuplc_admdvs", "") // 参保地医保区划 - .fluentPut("recer_sys_code", "yinyitong") // 接收方系统代码 - .fluentPut("dev_no", "") // 设备编号 - .fluentPut("dev_safe_info", "") // 设备安全信息 - .fluentPut("cainfo", "") // 数字签名信息 - .fluentPut("signtype", "") // 签名类型 - .fluentPut("infver", "V1.0") // 接口版本号 - .fluentPut("opter_type", 3) // 经办人类别 1-经办人;2-自助终端;3-移动终端 - .fluentPut("opter", "test") // 经办人 - .fluentPut("opter_name", "test") // 经办人姓名 - .fluentPut("inf_time", DateUtil.format(date, DatePattern.NORM_DATETIME_PATTERN)) // 交易时间 - .fluentPut("fixmedins_code", fixmeDinsCode) // 定点医药机构编号 - .fluentPut("fixmedins_name", fixmeDinsName) // 定点医药机构名称 - .fluentPut("sign_no", nonce) // 交易签到流水号 - .fluentPut("input", inputData); // 交易输入 - - String paramsStr = params.toString(SerializerFeature.WriteNullStringAsEmpty); - log.debug("[LOG9823][placeOrder][云南省局医保-API][获取支付授权] 请求入参: {}", paramsStr); - // 请求 - HttpResponse response = HttpRequest.post(ynChsConfig.getUrl()) - .header("Content-Type", "application/json") - .header("fixmedins_code", fixmeDinsCode) // 医保机构编码 - .header("infosyscode", ynChsConfig.getInfoSysCode()) // 服务商统一社会信用代码 - .header("hsf_nonce", nonce) // 校验码 非重复的随机字符串(十分钟内不能重复) - .header("hsf_timestamp", timestamp) // 当前时间戳(秒) - .header("hsf_signature", signature) // 加密生成的签名 - .body(paramsStr) - .execute(); - try { - String resultBody = response.body(); - log.debug("[LOG9823][placeOrder][云南省局医保-API][获取支付授权] 请求出参: {}", resultBody); - // 释放资源 - response.close(); - - JSONObject result = JSONObject.parseObject(resultBody); - - // TODO 萧道子 2025/10/22 : 判断逻辑 - - return result; - - } catch (Exception e) { - log.error("[LOG3760][placeOrder][云南省局医保-API][获取支付授权] 请求报错 ERR:{}", e.getMessage()); - e.printStackTrace(); - return null; - } - } - - - @Override - public JSONObject getPayToken() { - // 初始化 - this.init(); - - // 调用刷脸认证 - // ChsCheckResponse userInfo = padChsPayService.getInfoByFace(new ChsModel()); - - ChsCheckResponse userInfo = new ChsCheckResponse() - .setIdNo("530111199701031117") - .setUserName("薛家俊") - .setIdType("01") - .setEcToken("530000fec5tp6km4s626a3720a00005186b127") - .setAuthNo("ano3954912217759795201530000"); - - JSONObject params = new JSONObject() - .fluentPut("orgCodg", "") // 机构编码 - .fluentPut("feeType", "") // 费用类型 - .fluentPut("orgId", ynChsConfig.getFixmeDinsCode()) // 电子凭证机构号 - .fluentPut("idType", userInfo.getIdType()) // 证件类别 - .fluentPut("idNo", userInfo.getIdNo()) // 证件号码 - .fluentPut("userName", userInfo.getUserName()) // 用户姓名 - .fluentPut("ecToken", userInfo.getEcToken()) // 电子凭证授权 ecToken - .fluentPut("authNo", userInfo.getAuthNo()); // 实人认证流水号 - JSONObject inputData = new JSONObject().fluentPut("input", new JSONObject().fluentPut("data", params)); - String inputDataStr = inputData.toString(SerializerFeature.WriteNullStringAsEmpty); - - Pointer errMsg = new Memory(1024 * 10); - Pointer outputData = new Memory(1024 * 10); - - log.debug("[LOG1591][placeOrderByDll][云南省局医保DLL][BusinessHandle][终端获取支付令牌] 请求入参: {}", inputDataStr); - int state = dll.BusinessHandle(ynChsConfig.getFixmeDinsCode(), ynChsConfig.getInfoSysCode(), ynChsConfig.getInfoSysSign(), inputDataStr, outputData, errMsg); - String errMsgStr = errMsg.getString(0, CharsetConst.GB2312); - String outputDataStr = outputData.getString(0, CharsetConst.GB2312); - log.debug("[LOG1591][placeOrderByDll][云南省局医保DLL][BusinessHandle][终端获取支付令牌] 请求出参 data:{} errMsg:{}", outputDataStr, errMsgStr); - - if (state != 0) { - throw new RuntimeException("终端获取支付令牌失败:" + errMsgStr); - } - - JSONObject result = JSONObject.parseObject(outputDataStr); - - return result; - } - - - private void init() { - log.debug("[LOG4945][initPrinter][云南省局医保DLL][Init] 开始"); - Pointer errMsg = new Memory(1024 * 10); - int state = dll.Init(ynChsConfig.getFixmeDinsCode(), ynChsConfig.getInfoSysCode(), ynChsConfig.getInfoSysSign(), ynChsConfig.getUrl(), errMsg); - String errMsgStr = errMsg.getString(0, CharsetConst.GB2312); - log.debug("[LOG4945][initPrinter][云南省局医保DLL][Init] 结束 state:{} errMsg:{}", state, errMsgStr); - - if (state != 0) { - throw new RuntimeException("初始化医保动态库失败:" + errMsgStr); - } - } - -} diff --git a/src/main/java/com/dpkj/modules/chs/ynchs/service/impl/YnchspayByDllServiceImpl.java b/src/main/java/com/dpkj/modules/chs/ynchs/service/impl/YnchspayByDllServiceImpl.java new file mode 100644 index 0000000..81fd9c9 --- /dev/null +++ b/src/main/java/com/dpkj/modules/chs/ynchs/service/impl/YnchspayByDllServiceImpl.java @@ -0,0 +1,234 @@ +package com.dpkj.modules.chs.ynchs.service.impl; + +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.DateTime; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.RandomUtil; +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson.JSONObject; +import com.alibaba.fastjson.serializer.SerializerFeature; +import com.dpkj.common.config.PadChsConfig; +import com.dpkj.common.config.YnChsConfig; +import com.dpkj.common.constant.CharsetConst; +import com.dpkj.common.utils.IDGenerator; +import com.dpkj.modules.chs.padchs.constant.ChsDictEnum; +import com.dpkj.modules.chs.padchs.constant.NationECCodeConst; +import com.dpkj.modules.chs.padchs.response.ChsCheckResponse; +import com.dpkj.modules.chs.padchs.service.IPadChsPayService; +import com.dpkj.modules.chs.ynchs.constant.YnchsConst; +import com.dpkj.modules.chs.ynchs.dll.YnChsPayDll; +import com.dpkj.modules.chs.ynchs.service.IYnchspayByDllService; +import com.sun.jna.Memory; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import javax.annotation.PostConstruct; + +/** + * @Auther: 萧道子 + * @Date: 2025/8/7 14:46 + * @Description: + */ +@Slf4j +@Service +public class YnchspayByDllServiceImpl implements IYnchspayByDllService { + + // 缓冲区大小 1M + private final long bufferSize = 1024 * 1024; + + private YnChsPayDll.EcDll dll; + + @Autowired + private YnChsConfig ynChsConfig; + @Autowired + private PadChsConfig chsPadConfig; + @Autowired + private IPadChsPayService padChsPayService; + + + @PostConstruct + public void postConstruct() { + log.info("[YnchspayByWebapiServiceImpl][postConstruct][云南省局医保DLL] 初始化动态链接库"); + try { + dll = YnChsPayDll.instance(); + } catch (YnChsPayDll.DllRegistrationException e) { + log.error("[YnchspayByWebapiServiceImpl][postConstruct][云南省局医保DLL] 加载失败:{}", e.getMessage()); + throw new RuntimeException("云南省局医保DLL库加载失败:" + e.getMessage(), e); + } + } + + + @Override + public ChsCheckResponse getUserByFace() { + JSONObject ecinfo = this.chsApi1191(); + return new ChsCheckResponse() + .setAuthNo(ecinfo.getString("authno")) + .setEcToken(ecinfo.getString("ectoken")) + .setEcIndexNo(ecinfo.getString("ecIndexno")) + .setEmail(ecinfo.getString("email")) + .setUserName(ecinfo.getString("username")) + .setIdNo(ecinfo.getString("idno")) + .setIdType(ecinfo.getString("psn_cert_type")) + .setInsuOrg(ecinfo.getString("insuorg")) + .setBirthday(ecinfo.getString("birthday")) + .setGender(ecinfo.getString("gender")) + .setNationality(ecinfo.getString("nationality")); + } + + + /** + * 身份认证 + * + * @return com.alibaba.fastjson.JSONObject + * @author 萧道子 2025/10/24 + */ + public JSONObject chsApi1191() { + JSONObject inputData = new JSONObject() + .fluentPut("mdtrt_cert_type", "01") // 就诊凭证类型 + .fluentPut("cardtype", "") // 卡片类型 + .fluentPut("businesstype", ChsDictEnum.BusinessType.YY_301.str()) // 用码业务类型 + .fluentPut("operatorid", chsPadConfig.getOperatorId()) // 收款员编号 + .fluentPut("operatorname", chsPadConfig.getOperatorName()) // 收款员姓名 + .fluentPut("officeid", chsPadConfig.getOfficeId()) // 医保科室编号 + .fluentPut("officename", chsPadConfig.getOfficeName()) // 科室名称 + .fluentPut("transType", NationECCodeConst.TRANSTYPE_ALICHS_AUTH) // 交易类型 刷脸 + .fluentPut("deviceType", "SelfService") // 设备类型 + .fluentPut("outBizNo", IDGenerator.getSnowflakeIdToStr()) // 定点医药机构本次业务流水号 + .fluentPut("extData", ""); // 扩展参数 + JSONObject result = this.sendDll(YnchsConst.URL_1191, "1191", inputData); + JSONObject ecinfo = result.getJSONObject("ecinfo"); + return ecinfo; + } + + /** + * 人员信息获取 + * + * @param val : + * @return com.alibaba.fastjson.JSONObject + * @author 萧道子 2025/10/23 + */ + public JSONObject chsApi1101(ChsCheckResponse val) { + JSONObject inputData = new JSONObject() + .fluentPut("mdtrt_cert_type", "01") // 就诊凭证类型 + .fluentPut("mdtrt_cert_no", val.getEcToken()) // 就诊凭证编号-电子凭证令牌 + .fluentPut("card_sn", "") // 卡识别码 + .fluentPut("begntime", "") // 开始时间-获取历史参保信息时传入 + .fluentPut("psn_cert_type", val.getIdType()) // 人员证件类型-01代表身份证 + .fluentPut("certno", val.getIdNo()) // 证件号码 + .fluentPut("psn_name", val.getUserName()); // 人员姓名 + JSONObject result = this.sendDll(YnchsConst.URL_1101, "1101", inputData); + return result; + } + + /** + * 初始化函数 + * + * @param apiUrl : + * @return void + * @author 萧道子 2025/10/24 + */ + private void init(String apiUrl) { + log.debug("[LOG4945][initPrinter][云南省局医保DLL][Init] 开始"); + String url = ynChsConfig.getUrl() + apiUrl; + Memory errPointer = new Memory(bufferSize); + int state = dll.Init(ynChsConfig.getFixmeDinsCode(), ynChsConfig.getInfoSysCode(), ynChsConfig.getInfoSysSign(), url, errPointer); + String errValueStr = errPointer.getString(0, CharsetConst.GB2312); + errPointer.close(); + log.debug("[LOG4945][initPrinter][云南省局医保DLL][Init] 结束 state:{} errMsg:{}", state, errValueStr); + if (state != 0) { + throw new RuntimeException("初始化医保动态库失败:" + errValueStr); + } + } + + /** + * 发起请求 + * + * @param url : 请求地址 + * @param infno : 交易编码 + * @param inputData : 请求值 + * @return com.alibaba.fastjson.JSONObject + * @author 萧道子 2025/10/23 + */ + private JSONObject sendDll(String url, String infno, JSONObject inputData) { + // 一、初始化 + this.init(url); + + // 二、设置公共请求参数 + String infoSysSign = ynChsConfig.getInfoSysSign(); + String fixmeDinsCode = ynChsConfig.getFixmeDinsCode(); + String infoSysCode = ynChsConfig.getInfoSysCode(); + String cityCode = ynChsConfig.getCityCode(); + String fixmeDinsName = ynChsConfig.getFixmeDinsName(); + + String nonce = IDGenerator.getSnowflakeIdToStr(); + DateTime date = DateUtil.date(); + String timestamp = DateUtil.format(date, DatePattern.PURE_DATETIME_PATTERN); + + String msgid = fixmeDinsCode + timestamp + RandomUtil.randomNumbers(4); + + // 请求体参数 + JSONObject params = new JSONObject() + .fluentPut("infno", infno) // 交易编号 + .fluentPut("msgid", msgid) // 发送方报文ID 定点医药机构编号(12)+时间(14)+顺序号(4) 时间格式:yyyyMMddHHmmss + .fluentPut("mdtrtarea_admvs", cityCode) // 就医地医保区划 + .fluentPut("insuplc_admdvs", "") // 参保地医保区划 + .fluentPut("recer_sys_code", "yinyitong") // 接收方系统代码 + .fluentPut("dev_no", "") // 设备编号 + .fluentPut("dev_safe_info", "") // 设备安全信息 + .fluentPut("cainfo", "") // 数字签名信息 + .fluentPut("signtype", "") // 签名类型 + .fluentPut("infver", "V1.0") // 接口版本号 + .fluentPut("opter_type", "2") // 经办人类别 1-经办人;2-自助终端;3-移动终端 + .fluentPut("opter", "test") // 经办人 + .fluentPut("opter_name", "test") // 经办人姓名 + .fluentPut("inf_time", DateUtil.format(date, DatePattern.NORM_DATETIME_PATTERN)) // 交易时间 + .fluentPut("fixmedins_code", fixmeDinsCode) // 定点医药机构编号 + .fluentPut("fixmedins_name", fixmeDinsName) // 定点医药机构名称 + .fluentPut("sign_no", nonce) // 交易签到流水号 + .fluentPut("input", new JSONObject().fluentPut("data", inputData)); // 交易输入 + String paramsStr = params.toString(SerializerFeature.WriteNullStringAsEmpty); + + // 三、发起请求 + log.debug("[LOG1591][sendHttp][云南省局医保DLL][{}] 请求入参: {}", infno, paramsStr); + + Memory errPointer = new Memory(bufferSize); + Memory outPointer = new Memory(bufferSize); + int state = dll.BusinessHandle(fixmeDinsCode, infoSysCode, infoSysSign, paramsStr, outPointer, errPointer); + String errValueStr = errPointer.getString(0, CharsetConst.GB2312); + String outValueStr = outPointer.getString(0, CharsetConst.GB2312); + errPointer.close(); + outPointer.close(); + + log.debug("[LOG1591][sendHttp][云南省局医保DLL][{}] 请求出参 state:{} errMsg:{} data:{} ", infno, state, errValueStr, outValueStr); + + // 四、处理响应 + if (state != 0) { + log.error("[LOG8761][sendDll][云南省局医保DLL][{}] 医保通用接口BusinessHandle调用失败 state:{}", infno, state); + throw new RuntimeException("医保接口请求失败!"); + } + + if (StrUtil.isEmpty(outValueStr)) { + log.error("[LOG8762][sendDll][云南省局医保DLL][{}] 医保接口响应数据为空 state:{} ", infno, state); + throw new RuntimeException("医保接口响应数据为空:" + errValueStr); + } + + JSONObject result; + try { + result = JSONObject.parseObject(outValueStr); + } catch (Exception e) { + log.error("[LOG4410][sendDll][云南省局医保DLL][{}] 医保响应数转JSON失败 ERR:{}", infno, e.getMessage()); + throw new RuntimeException("医保响应数据解析失败!"); + } + + if (result.getInteger("infcode") != 0) { + String errmsg = result.getString("err_msg"); + log.error("[LOG4411][sendDll][云南省局医保DLL][{}] 医保方系统错误 ERR:{}", infno, errmsg); + throw new RuntimeException("医保请求失败:" + errmsg); + } + + JSONObject output = result.getJSONObject("output"); + return output; + } + +} diff --git a/src/main/java/com/dpkj/modules/chs/ynchs/service/impl/YnchspayByWebapiServiceImpl.java b/src/main/java/com/dpkj/modules/chs/ynchs/service/impl/YnchspayByWebapiServiceImpl.java new file mode 100644 index 0000000..6727ebc --- /dev/null +++ b/src/main/java/com/dpkj/modules/chs/ynchs/service/impl/YnchspayByWebapiServiceImpl.java @@ -0,0 +1,233 @@ +package com.dpkj.modules.chs.ynchs.service.impl; + +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.DateTime; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.RandomUtil; +import cn.hutool.crypto.SecureUtil; +import cn.hutool.http.HttpRequest; +import cn.hutool.http.HttpResponse; +import com.alibaba.fastjson.JSONObject; +import com.alibaba.fastjson.serializer.SerializerFeature; +import com.dpkj.common.config.YnChsConfig; +import com.dpkj.common.utils.IDGenerator; +import com.dpkj.modules.chs.padchs.response.ChsCheckResponse; +import com.dpkj.modules.chs.padchs.service.IPadChsPayService; +import com.dpkj.modules.chs.ynchs.constant.YnchsConst; +import com.dpkj.modules.chs.ynchs.service.IYnchspayByWebapiService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import javax.annotation.PostConstruct; + +/** + * @Auther: 萧道子 + * @Date: 2025/8/7 14:46 + * @Description: + */ +@Slf4j +@Service +public class YnchspayByWebapiServiceImpl implements IYnchspayByWebapiService { + @Autowired + private YnChsConfig ynChsConfig; + @Autowired + private IPadChsPayService padChsPayService; + + @PostConstruct + public void postConstruct() { + + } + + + /** + * 人员信息获取 + * + * @param val : + * @return com.alibaba.fastjson.JSONObject + * @author 萧道子 2025/10/23 + */ + private JSONObject chsApi1101(ChsCheckResponse val) { + JSONObject inputData = new JSONObject() + .fluentPut("mdtrt_cert_type", "01") // 就诊凭证类型 + .fluentPut("mdtrt_cert_no", val.getEcToken()) // 就诊凭证编号-电子凭证令牌 + .fluentPut("card_sn", "") // 卡识别码 + .fluentPut("begntime", "") // 开始时间-获取历史参保信息时传入 + .fluentPut("psn_cert_type", val.getIdType()) // 人员证件类型-01代表身份证 + .fluentPut("certno", val.getIdNo()) // 证件号码 + .fluentPut("psn_name", val.getUserName()); // 人员姓名 + + JSONObject result = this.sendHttp(YnchsConst.URL_1101, "1101", inputData); + return result; + } + + + /** + * 订单支付-费用明细上传 + * + * @return com.alibaba.fastjson.JSONObject + * @author 萧道子 2025/10/23 + */ + private JSONObject chsApi6201(JSONObject val) { + String fixmeDinsCode = ynChsConfig.getFixmeDinsCode(); + JSONObject params = new JSONObject() + .fluentPut("orgCodg", fixmeDinsCode) // 机构编码-医保分配 字符型40-Y + .fluentPut("orgId", "") // 电子凭证机构号-电子凭证中台分配,待机构电子凭证建设完毕后可获取该机构号 字符型40 + .fluentPut("psnNo", "") // 人员编号 字符型30-Y + .fluentPut("insutype", "") // 险种类型-字典险种类型 (insutype) 字符型6-Y + .fluentPut("medOrgOrd", "") // 医疗机构订单号-院内产生惟一流水,可关联到一次结算记录,结算成功回调入参返回 字符型40-Y + .fluentPut("initRxOrd", "") // 要续方的原处方流水-rxCircFlag 为1时必传,续方时必传 字符型40 + .fluentPut("rxCircFlag", "") // 电子处方流转标志-1:电子处方 ,0不是电子处方,默认0 字符型1 + .fluentPut("begntime", "") // 开始时间-挂号时间yyyy-MM-dd HH:mm:ss 字符型19-Y + .fluentPut("idNo", "") // 证件号码 字符型40-Y + .fluentPut("userName", "") // 用户姓名 字符型40-Y + .fluentPut("idType", "") // 证件类别-字典人员证件类型(psn_cert_type) 字符型3-Y + .fluentPut("ecToken", "") // 电子凭证授权ecToken-电子凭证解码返回 字符型64-Y + .fluentPut("insuCode", "") // 就诊参保地行政区划-同核心FSI接口入参mdtrtarea_admvs 字符型6-Y + .fluentPut("iptOtpNo", "") // 住院/门诊号-院内唯一流水 字符型30-Y + .fluentPut("atddrNo", "") // 医师编码 字符型30 + .fluentPut("drName", "") // 医师姓名 字符型50 + .fluentPut("deptCode", "") // 科室编码 字符型30 + .fluentPut("deptName", "") // 科室名称 字符型100-Y + .fluentPut("caty", "") // 科别 字符型6-Y + .fluentPut("mdtrtId", "") // 就诊ID 字符型30 + .fluentPut("medType", "") // 医疗类别-字典医疗类别(med_type) 字符型6-Y + .fluentPut("feeType", "") // 费用类型-字典费用类型(feeType) 字符型3-Y + .fluentPut("medfeeSumamt", "") // 医疗费总额 数值型16,2-Y + .fluentPut("acctUsedFlag", "") // 个人账户使用标志-药店上传费用时必填 字符型1-Y + .fluentPut("mainCondDscr", "") // 主要病情描述 字符型1000 + .fluentPut("diseCodg", "") // 病种编码-按照标准编码填写:按病种结算病种目录代码(bydise_setl_list_code)、门诊慢特病病种目录代码(opsp_dise_cod) 字符型30-Y + .fluentPut("diseName", "") // 病种名称 字符型500-Y + .fluentPut("psnSetlway", "") // 个人结算方式 字符型6-Y + .fluentPut("chrgBchno", "") // 收费批次号 字符型30-Y + .fluentPut("pubHospRfomFlag", "") // 公立医院改革标志-可参考FSI的接口要求 字符型6-Y + .fluentPut("invono", "") // 发票号 字符型20 + .fluentPut("endtime", "") // 出院时间-yyyy-MM-dd HH:mm:ss 字符型19 + .fluentPut("fulamtOwnpayAmt", "") // 全自费金额-住院结算时需要 数值型16,2 + .fluentPut("overlmtSelfpay", "") // 超限价金额-住院结算时需要 数值型16,2 + .fluentPut("preselfpayAmt", "") // 先行自付金额-住院结算时需要 数值型16,2 + .fluentPut("inscpScpAmt", "") // 符合政策范围金额-住院结算时需要 数值型16,2 + .fluentPut("oprnOprtCode", "") // 手术操作代码-住院结算时需要 字符型30 + .fluentPut("oprnOprtName", "") // 手术操作名称-住院结算时需要 字符型500 + .fluentPut("fpscNo", "") // 计划生育服务证号-住院结算时需要 字符型50 + .fluentPut("latechbFlag", "") // 晚育标志-住院结算时需要 字符型3 + .fluentPut("gesoVal", "") // 孕周数-住院结算时需要 数值型2 + .fluentPut("fetts", "") // 胎次-住院结算时需要 数值型3-Y + .fluentPut("fetusCnt", "") // 胎儿数-住院结算时需要 数值型3 + .fluentPut("pretFlag", "") // 早产标志-住院结算时需要 字符型3 + .fluentPut("birctrlType", "") // 计划生育手术类别-生育门诊按需录入 字符型6 + .fluentPut("birctrlMatnDate", "") // 计划生育手术或生育日期-生育门诊按需录入,yyyy-MM-dd 字符型10 + .fluentPut("copFlag", "") // 伴有并发症标志-住院结算时需要 字符型3 + .fluentPut("dscgDeptCodg", "") // 出院科室编码-住院结算时需要 字符型30 + .fluentPut("dscgDeptName", "") // 出院科室名称-住院结算时需要 字符型100 + .fluentPut("dscgDed", "") // 出院床位-住院结算时需要 字符型50 + .fluentPut("dscgWay", "") // 离院方式-住院结算时需要 字符型8 + .fluentPut("dieDate", "") // 死亡日期-yyyy-MM-dd 字符型10 + .fluentPut("matnType", "") // 生育类别-住院结算时需要 字符型6-Y + .fluentPut("expContent", "") // 扩展参数-可参考FSI的接口要求 字符型4000 + .fluentPut("midSetlFlag", "") // 中途结算标志-字典中途结算标志(mid_setl_flag) 字符型3 + .fluentPut("diseinfoList", "") // 诊断或症状明细-Y-见下方diseinfoList定义 + .fluentPut("feedetailList", "") // 费用明细-Y-见下方feedetailList定义 + .fluentPut("admDiagDscr", "") // 入院诊断描述-住院结算时需要 字符型200 + .fluentPut("admDeptCodg", "") // 入院科室编码-住院结算时需要 字符型30 + .fluentPut("admDeptName", "") // 入院科室名称-住院结算时需要 字符型100 + .fluentPut("admBed", "") // 入院床位-住院结算时需要 字符型30 + .fluentPut("payAuthNo", "") // 支付授权码-线上授权返回 字符型40 + .fluentPut("uldLatlnt", "") // 经纬度-格式:经度,纬度如:118.096435,24.485407定位失败情况下传:0,0 字符型21 + .fluentPut("mdtrtCertType", "") // 就诊凭证类型-“00” 无就诊介质,“01” 电子凭证,“02”居民身份证,“03”社会保障卡 字符型2 + .fluentPut("insuplcAdmdvs", "") // 用户参保地行政区划-同核心FSI参数insuplc_admdvs 字符型6-Y + .fluentPut("dscgFlag", "") // 出院登记标志-住院结算时需要,0:由中台调用出院登记1:由定点调用出院登记 字符型1 + .fluentPut("opterType", "") // 操作员类别-为空时使用移动支付中心默认 字符型10 + .fluentPut("devSafeInfo", "") // 设备安全编码-为空时使用移动支付中心默默认 字符型30 + .fluentPut("setlType", "") // 结算类别-OWN:自费类别放空默认为医保订单 字符型30 + .fluentPut("fixmedinsMdtrtId", "") // 医药机构就诊ID-setlType为OWN且当地要求自费数据要上传核心时必传,参考FSI要求 字符型30 + .fluentPut("ownpayType", "") // 自费类型-setlType为OWN且当地要求自费数据要上传核心时传可按需传,参考FSI要求 字符型3 + .fluentPut("elecBillnoCode", "") // 电子票据号码-setlType为OWN且当地要求自费数据要上传核心时传可按需传,参考FSI要求。如果是纸质发票则填写纸质发票号码 字符型50 + .fluentPut("elecBillCode", "") // 电子票据代码-setlType为OWN且当地要求自费数据要上传核心时传可按需传,参考FSI要求 字符型50 + .fluentPut("elecBillChkcode", "") // 电子票据校验码-setlType为OWN且当地要求自费数据要上传核心时传可按需传,参考FSI要求 字符型6 + .fluentPut("onlineDrugChnlAppId", "") // 线上购药渠道id-线上购药业务时,必传,见移动支付反馈单 字符型40 + .fluentPut("drugCrtfCodg", "") // 线上购药渠道认证编码-线上购药业务时,必传,见移动支付线上购药反馈单 字符型40 + ; + + JSONObject result = sendHttp(YnchsConst.URL_6201, "6201", params); + + return null; + } + + + /** + * 发起请求 + * + * @param url : + * @param infno : + * @param inputData : + * @return com.alibaba.fastjson.JSONObject + * @author 萧道子 2025/10/23 + */ + private JSONObject sendHttp(String url, String infno, JSONObject inputData) { + + // 请求头参数 + String infoSysSign = ynChsConfig.getInfoSysSign(); + String fixmeDinsCode = ynChsConfig.getFixmeDinsCode(); + String infoSysCode = ynChsConfig.getInfoSysCode(); + String baseUrl = ynChsConfig.getUrl(); + String cityCode = ynChsConfig.getCityCode(); + String fixmeDinsName = ynChsConfig.getFixmeDinsName(); + + String nonce = IDGenerator.getSnowflakeIdToStr(); + DateTime date = DateUtil.date(); + String timestamp = DateUtil.format(date, DatePattern.PURE_DATETIME_PATTERN); + String signature = SecureUtil.sha256(timestamp + infoSysSign + nonce); + + String msgid = fixmeDinsCode + timestamp + RandomUtil.randomNumbers(4); + + // 请求体参数 + JSONObject params = new JSONObject() + .fluentPut("infno", infno) // 交易编号 + .fluentPut("msgid", msgid) // 发送方报文ID 定点医药机构编号(12)+时间(14)+顺序号(4) 时间格式:yyyyMMddHHmmss + .fluentPut("mdtrtarea_admvs", cityCode) // 就医地医保区划 + .fluentPut("insuplc_admdvs", "") // 参保地医保区划 + .fluentPut("recer_sys_code", "yinyitong") // 接收方系统代码 + .fluentPut("dev_no", "") // 设备编号 + .fluentPut("dev_safe_info", "") // 设备安全信息 + .fluentPut("cainfo", "") // 数字签名信息 + .fluentPut("signtype", "") // 签名类型 + .fluentPut("infver", "V1.0") // 接口版本号 + .fluentPut("opter_type", 3) // 经办人类别 1-经办人;2-自助终端;3-移动终端 + .fluentPut("opter", "test") // 经办人 + .fluentPut("opter_name", "test") // 经办人姓名 + .fluentPut("inf_time", DateUtil.format(date, DatePattern.NORM_DATETIME_PATTERN)) // 交易时间 + .fluentPut("fixmedins_code", fixmeDinsCode) // 定点医药机构编号 + .fluentPut("fixmedins_name", fixmeDinsName) // 定点医药机构名称 + .fluentPut("sign_no", nonce) // 交易签到流水号 + .fluentPut("input", new JSONObject().fluentPut("data", inputData)); // 交易输入 + String paramsStr = params.toString(SerializerFeature.WriteNullStringAsEmpty); + + try { + // 请求 + log.debug("[LOG7900][sendHttp][云南省局医保-API][{}] 请求入参: {}", infno, paramsStr); + HttpResponse response = HttpRequest.post(baseUrl + url) + .header("Content-Type", "application/json") + .header("fixmedins_code", fixmeDinsCode) // 医保机构编码 + .header("infosyscode", infoSysCode) // 服务商统一社会信用代码 + .header("hsf_nonce", nonce) // 校验码 非重复的随机字符串(十分钟内不能重复) + .header("hsf_timestamp", timestamp) // 当前时间戳(秒) + .header("hsf_signature", signature) // 加密生成的签名 + .body(paramsStr) + .execute(); + String resultBody = response.body(); + log.debug("[LOG7900][sendHttp][云南省局医保-API][{}] 请求出参: {}", infno, resultBody); + // 释放资源 + response.close(); + + // 转换参数 + JSONObject result = JSONObject.parseObject(resultBody); + return result; + } catch (Exception e) { + // e.printStackTrace(); + log.error("[LOG3010][sendHttp][云南省局医保-API][{}] 请求失败 ERR:{}", infno, e.getMessage()); + throw new RuntimeException("医保接口请求失败!"); + } + } + +} diff --git a/src/main/java/com/dpkj/modules/chs/ynchs/utils/BCUtils.java b/src/main/java/com/dpkj/modules/chs/ynchs/utils/BCUtils.java new file mode 100644 index 0000000..919f2a2 --- /dev/null +++ b/src/main/java/com/dpkj/modules/chs/ynchs/utils/BCUtils.java @@ -0,0 +1,39 @@ +package com.dpkj.modules.chs.ynchs.utils; + +import org.bouncycastle.jce.provider.BouncyCastleProvider; + +import javax.crypto.Cipher; +import javax.crypto.NoSuchPaddingException; +import java.security.*; + +public class BCUtils { + + static { + if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME)==null){ + Security.addProvider(new BouncyCastleProvider()); + } + } + + public static Cipher getCipher(final String algorithm) { + try { + return Cipher.getInstance(algorithm, BouncyCastleProvider.PROVIDER_NAME); + } catch (final NoSuchAlgorithmException | NoSuchPaddingException | NoSuchProviderException e) { + throw new IllegalArgumentException(e); + } + } + + public static MessageDigest getMessageDigest(final String algorithm) { + try { + return MessageDigest.getInstance(algorithm, BouncyCastleProvider.PROVIDER_NAME); + } catch (final NoSuchAlgorithmException|NoSuchProviderException e) { + throw new IllegalArgumentException(e); + } + } + public static Signature getSignature(final String algorithm) { + try { + return Signature.getInstance(algorithm, BouncyCastleProvider.PROVIDER_NAME); + } catch (final NoSuchAlgorithmException|NoSuchProviderException e) { + throw new IllegalArgumentException(e); + } + } +} diff --git a/src/main/java/com/dpkj/modules/chs/ynchs/utils/EasyGmUtils.java b/src/main/java/com/dpkj/modules/chs/ynchs/utils/EasyGmUtils.java new file mode 100644 index 0000000..c93996f --- /dev/null +++ b/src/main/java/com/dpkj/modules/chs/ynchs/utils/EasyGmUtils.java @@ -0,0 +1,361 @@ +package com.dpkj.modules.chs.ynchs.utils; + +import org.bouncycastle.asn1.ASN1EncodableVector; +import org.bouncycastle.asn1.ASN1Integer; +import org.bouncycastle.asn1.ASN1Sequence; +import org.bouncycastle.asn1.DERSequence; +import org.bouncycastle.asn1.gm.GMNamedCurves; +import org.bouncycastle.asn1.x9.X9ECParameters; +import org.bouncycastle.crypto.InvalidCipherTextException; +import org.bouncycastle.crypto.engines.SM2Engine; +import org.bouncycastle.crypto.params.ECDomainParameters; +import org.bouncycastle.crypto.params.ECPrivateKeyParameters; +import org.bouncycastle.crypto.params.ECPublicKeyParameters; +import org.bouncycastle.crypto.params.ParametersWithRandom; +import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey; +import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey; +import org.bouncycastle.jcajce.spec.SM2ParameterSpec; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.jce.spec.ECParameterSpec; +import org.bouncycastle.jce.spec.ECPrivateKeySpec; +import org.bouncycastle.jce.spec.ECPublicKeySpec; +import org.bouncycastle.util.BigIntegers; +import org.bouncycastle.util.encoders.Hex; + +import javax.crypto.Cipher; +import javax.crypto.spec.SecretKeySpec; +import java.io.IOException; +import java.math.BigInteger; +import java.security.Key; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.SecureRandom; +import java.security.Signature; +import java.util.Arrays; +import java.util.Base64; + +/** + * @Author: fuchengmu + * @Date: 2019-10-05 14:58 + *

+ * 用BC的注意点: + * 这个版本的BC对SM3withSM2的结果为asn1格式的r和s,如果需要直接拼接的r||s需要自己转换。下面rsAsn1ToPlainByteArray、rsPlainByteArrayToAsn1就在干这事。 + * 这个版本的BC对SM2的结果为C1||C2||C3,据说为旧标准,新标准为C1||C3||C2,用新标准的需要自己转换。下面changeC1C2C3ToC1C3C2、changeC1C3C2ToC1C2C3就在干这事。 + */ +public class EasyGmUtils { + private static X9ECParameters x9ECParameters = GMNamedCurves.getByName("sm2p256v1"); + private static ECDomainParameters ecDomainParameters = new ECDomainParameters(x9ECParameters.getCurve(), x9ECParameters.getG(), x9ECParameters.getN()); + private static ECParameterSpec ecParameterSpec = new ECParameterSpec(x9ECParameters.getCurve(), x9ECParameters.getG(), x9ECParameters.getN()); + + + public static byte[] signSm3WithSm2(byte[] msg, byte[] userId, byte[] privateKeyBytes) { + BCECPrivateKey bcecPrivateKey = getPrivatekeyFromD(BigIntegers.fromUnsignedByteArray(privateKeyBytes)); + return signSm3WithSm2(msg, userId, bcecPrivateKey); + + } + + /** + * @param msg + * @param userId + * @param privateKey + * @return r||s,直接拼接byte数组的rs + */ + public static byte[] signSm3WithSm2(byte[] msg, byte[] userId, PrivateKey privateKey) { + return rsAsn1ToPlainByteArray(signSm3WithSm2Asn1Rs(msg, userId, privateKey)); + } + + + /** + * @param msg + * @param userId + * @param privateKey + * @return rs in asn1 format + */ + public static byte[] signSm3WithSm2Asn1Rs(byte[] msg, byte[] userId, PrivateKey privateKey) { + try { + SM2ParameterSpec parameterSpec = new SM2ParameterSpec(userId); + Signature signer = BCUtils.getSignature("SM3withSM2"); +// signer.setParameter(parameterSpec); + signer.initSign(privateKey, new SecureRandom()); + signer.update(msg, 0, msg.length); + byte[] sig = signer.sign(); + return sig; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static boolean verifySm3WithSm2(byte[] msg, byte[] userId, byte[] rs, byte[] publicKeyBytes) { + if (publicKeyBytes.length != 64 && publicKeyBytes.length != 65) { + throw new RuntimeException("err key length"); + } + + BigInteger x, y; + if (publicKeyBytes.length > 64) { + x = BigIntegers.fromUnsignedByteArray(publicKeyBytes, 1, 32); + y = BigIntegers.fromUnsignedByteArray(publicKeyBytes, 33, 32); + } else { + x = BigIntegers.fromUnsignedByteArray(publicKeyBytes, 0, 32); + y = BigIntegers.fromUnsignedByteArray(publicKeyBytes, 32, 32); + } + BCECPublicKey bcecPublicKey = getPublickeyFromXY(x, y); + + return verifySm3WithSm2(msg, userId, rs, bcecPublicKey); + } + + /** + * @param msg + * @param userId + * @param rs r||s,直接拼接byte数组的rs + * @param publicKey + * @return + */ + public static boolean verifySm3WithSm2(byte[] msg, byte[] userId, byte[] rs, PublicKey publicKey) { + return verifySm3WithSm2Asn1Rs(msg, userId, rsPlainByteArrayToAsn1(rs), publicKey); + } + + /** + * @param msg + * @param userId + * @param rs in asn1 format + * @param publicKey + * @return + */ + public static boolean verifySm3WithSm2Asn1Rs(byte[] msg, byte[] userId, byte[] rs, PublicKey publicKey) { + try { + + Signature verifier = BCUtils.getSignature("SM3withSM2"); +// verifier.setParameter(parameterSpec); + verifier.initVerify(publicKey); + verifier.update(msg, 0, msg.length); + return verifier.verify(rs); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** + * bc加解密使用旧标c1||c2||c3,此方法在加密后调用,将结果转化为c1||c3||c2 + * + * @param c1c2c3 + * @return + */ + private static byte[] changeC1C2C3ToC1C3C2(byte[] c1c2c3) { + final int c1Len = (x9ECParameters.getCurve().getFieldSize() + 7) / 8 * 2 + 1; // sm2p256v1的这个固定65。可看GMNamedCurves、ECCurve代码。 + final int c3Len = 32; // new SM3Digest().getDigestSize(); + byte[] result = new byte[c1c2c3.length]; + System.arraycopy(c1c2c3, 0, result, 0, c1Len); // c1 + System.arraycopy(c1c2c3, c1c2c3.length - c3Len, result, c1Len, c3Len); // c3 + System.arraycopy(c1c2c3, c1Len, result, c1Len + c3Len, c1c2c3.length - c1Len - c3Len); // c2 + return result; + } + + + /** + * bc加解密使用旧标c1||c3||c2,此方法在解密前调用,将密文转化为c1||c2||c3再去解密 + * + * @param c1c3c2 + * @return + */ + private static byte[] changeC1C3C2ToC1C2C3(byte[] c1c3c2) { + final int c1Len = (x9ECParameters.getCurve().getFieldSize() + 7) / 8 * 2 + 1; // sm2p256v1的这个固定65。可看GMNamedCurves、ECCurve代码。 + final int c3Len = 32; // new SM3Digest().getDigestSize(); + byte[] result = new byte[c1c3c2.length]; + System.arraycopy(c1c3c2, 0, result, 0, c1Len); // c1: 0->65 + System.arraycopy(c1c3c2, c1Len + c3Len, result, c1Len, c1c3c2.length - c1Len - c3Len); // c2 + System.arraycopy(c1c3c2, c1Len, result, c1c3c2.length - c3Len, c3Len); // c3 + return result; + } + + private final static int RS_LEN = 32; + + private static byte[] bigIntToFixexLengthBytes(BigInteger rOrS) { + // for sm2p256v1, n is 00fffffffeffffffffffffffffffffffff7203df6b21c6052b53bbf40939d54123, + // r and s are the result of mod n, so they should be less than n and have length<=32 + byte[] rs = rOrS.toByteArray(); + if (rs.length == RS_LEN) { + return rs; + } else if (rs.length == RS_LEN + 1 && rs[0] == 0) { + return Arrays.copyOfRange(rs, 1, RS_LEN + 1); + } else if (rs.length < RS_LEN) { + byte[] result = new byte[RS_LEN]; + Arrays.fill(result, (byte) 0); + System.arraycopy(rs, 0, result, RS_LEN - rs.length, rs.length); + return result; + } else { + throw new RuntimeException("err rs: " + Hex.toHexString(rs)); + } + } + + /** + * BC的SM3withSM2签名得到的结果的rs是asn1格式的,这个方法转化成直接拼接r||s + * + * @param rsDer rs in asn1 format + * @return sign result in plain byte array + */ + private static byte[] rsAsn1ToPlainByteArray(byte[] rsDer) { + ASN1Sequence seq = ASN1Sequence.getInstance(rsDer); + byte[] r = bigIntToFixexLengthBytes(ASN1Integer.getInstance(seq.getObjectAt(0)).getValue()); + byte[] s = bigIntToFixexLengthBytes(ASN1Integer.getInstance(seq.getObjectAt(1)).getValue()); + byte[] result = new byte[RS_LEN * 2]; + System.arraycopy(r, 0, result, 0, r.length); + System.arraycopy(s, 0, result, RS_LEN, s.length); + return result; + } + + /** + * BC的SM3withSM2验签需要的rs是asn1格式的,这个方法将直接拼接r||s的字节数组转化成asn1格式 + * + * @param sign in plain byte array + * @return rs result in asn1 format + */ + private static byte[] rsPlainByteArrayToAsn1(byte[] sign) { + if (sign.length != RS_LEN * 2) { + throw new RuntimeException("err rs. "); + } + BigInteger r = new BigInteger(1, Arrays.copyOfRange(sign, 0, RS_LEN)); + BigInteger s = new BigInteger(1, Arrays.copyOfRange(sign, RS_LEN, RS_LEN * 2)); + ASN1EncodableVector v = new ASN1EncodableVector(); + v.add(new ASN1Integer(r)); + v.add(new ASN1Integer(s)); + try { + return new DERSequence(v).getEncoded("DER"); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public static BCECPrivateKey getPrivatekeyFromD(BigInteger d) { + ECPrivateKeySpec ecPrivateKeySpec = new ECPrivateKeySpec(d, ecParameterSpec); + return new BCECPrivateKey("EC", ecPrivateKeySpec, BouncyCastleProvider.CONFIGURATION); + } + + public static BCECPublicKey getPublickeyFromXY(BigInteger x, BigInteger y) { + ECPublicKeySpec ecPublicKeySpec = new ECPublicKeySpec(x9ECParameters.getCurve().createPoint(x, y), ecParameterSpec); + return new BCECPublicKey("EC", ecPublicKeySpec, BouncyCastleProvider.CONFIGURATION); + } + + /** + * c1||c3||c2 + * + * @param data + * @param key + * @return + */ + public static byte[] sm2Decrypt(byte[] data, PrivateKey key) { + return sm2DecryptOld(changeC1C3C2ToC1C2C3(data), key); + } + + /** + * c1||c3||c2 + * + * @param data + * @param key + * @return + */ + + public static byte[] sm2Encrypt(byte[] data, PublicKey key) { + return changeC1C2C3ToC1C3C2(sm2EncryptOld(data, key)); + } + + /** + * c1||c2||c3 + * + * @param data + * @param key + * @return + */ + public static byte[] sm2EncryptOld(byte[] data, PublicKey key) { + BCECPublicKey localECPublicKey = (BCECPublicKey) key; + ECPublicKeyParameters ecPublicKeyParameters = new ECPublicKeyParameters(localECPublicKey.getQ(), ecDomainParameters); + SM2Engine sm2Engine = new SM2Engine(); + sm2Engine.init(true, new ParametersWithRandom(ecPublicKeyParameters, new SecureRandom())); + try { + return sm2Engine.processBlock(data, 0, data.length); + } catch (InvalidCipherTextException e) { + throw new RuntimeException(e); + } + } + + /** + * c1||c2||c3 + * + * @param data + * @param key + * @return + */ + public static byte[] sm2DecryptOld(byte[] data, PrivateKey key) { + BCECPrivateKey localECPrivateKey = (BCECPrivateKey) key; + ECPrivateKeyParameters ecPrivateKeyParameters = new ECPrivateKeyParameters(localECPrivateKey.getD(), ecDomainParameters); + SM2Engine sm2Engine = new SM2Engine(); + sm2Engine.init(false, ecPrivateKeyParameters); + try { + return sm2Engine.processBlock(data, 0, data.length); + } catch (InvalidCipherTextException e) { + throw new RuntimeException(e); + } + } + + public static byte[] sm4Encrypt(byte[] keyBytes, byte[] plain) { + if (keyBytes.length != 16) { + throw new RuntimeException("err key length"); + } +// if (plain.length % 16 != 0) throw new RuntimeException("err data length"); + + try { + Key key = new SecretKeySpec(keyBytes, "SM4"); + + Cipher out = BCUtils.getCipher("SM4/ECB/PKCS7Padding"); + out.init(Cipher.ENCRYPT_MODE, key); + return out.doFinal(plain); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static byte[] sm4Decrypt(byte[] keyBytes, byte[] cipher) { +// if (keyBytes.length != 16) throw new RuntimeException("err key length"); + if (cipher.length % 16 != 0) { + throw new RuntimeException("err data length"); + } + + try { + Key key = new SecretKeySpec(keyBytes, "SM4"); + Cipher in = BCUtils.getCipher("SM4/ECB/PKCS7Padding"); + in.init(Cipher.DECRYPT_MODE, key); + return in.doFinal(cipher); + + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static void main(String[] args) { + byte[] msg = "1234567890".getBytes(); + byte[] userId = "D91CEB11EE62219CD91CEB11EE62219C".getBytes(); + byte[] prvkey = Hex.decode("5B015D0B2C0B6BEBB27EA8"); + byte[] pubkey = Hex.decode("409AEFE32E150677FC2372B758178ECBAD3BBED494975596B3CD603C36F9ED7E690A5B052AFDBD86D46288FA9919BA05F3D48F6532EB28DC02D0DAA35689A50C"); + + prvkey = Base64.getDecoder().decode("JShsBOJL0RgPAoPttEB1hgtPAvCikOl0V1oTOYL7k5U="); + pubkey = Base64.getDecoder().decode("BE8d9gaaCGrE4dHErWCjqyahm6X8l6Rd7fOGx0gNyrGPp0XDoPbbpu1pk9A2fZ9rEsBtwB1Aecnto/gH4h+T7cY="); + byte[] asig = signSm3WithSm2(msg, userId, prvkey); + System.out.println(Hex.toHexString(asig)); + boolean verified = verifySm3WithSm2(msg, userId, asig, pubkey); + System.out.println(verified); + + + String plainString = "1234567890abcdef"; + byte[] plain = plainString.getBytes(); + System.out.println(new String(plain)); +// byte[] key = Hex.decode("0123456789abcdeffedcba9876543210"); + byte[] sm4key = "0123456789abcdef".getBytes(); +// byte[] cipher = Hex.decode("595298c7c6fd271f0402f804c33d3f66"); + byte[] bs = sm4Encrypt(sm4key, plain); + System.out.println(Hex.toHexString(bs)); + ; + bs = sm4Decrypt(sm4key, bs); + System.out.println(new String(bs)); + System.out.println(Hex.toHexString(bs)); + } + +} diff --git a/src/main/java/com/dpkj/modules/chs/ynchs/utils/HseEncAndDecUtil.java b/src/main/java/com/dpkj/modules/chs/ynchs/utils/HseEncAndDecUtil.java new file mode 100644 index 0000000..aea6fa1 --- /dev/null +++ b/src/main/java/com/dpkj/modules/chs/ynchs/utils/HseEncAndDecUtil.java @@ -0,0 +1,278 @@ +package com.dpkj.modules.chs.ynchs.utils; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import org.bouncycastle.util.encoders.Hex; + +import java.util.Base64; +import java.util.Iterator; +import java.util.Map; + +/** + * @author: muweng + * @date: 2020/1/20 21:33 + * @description: 电子凭证加解密和. + */ +public class HseEncAndDecUtil { + + + /** + * sm2签名 + * + * @param message 未加密报文 + * @param sm4key 渠道sm4密钥 + * @param prvKey 渠道私钥 + * @return 签名串 String + * @throws Exception + */ + public static String signature(String message, String sm4key, String prvKey) { + byte[] messageByte; + try { + JSONObject jsonObject = JSON.parseObject(message); + removeEmpty(jsonObject); + messageByte = SignUtil.getSignText(jsonObject, sm4key).getBytes("UTF-8"); + } catch (Exception e) { + messageByte = message.getBytes(); + } + byte[] chnlSecretByte = sm4key.getBytes(); + byte[] prvkey = Base64.getDecoder().decode(prvKey); + return Base64.getEncoder().encodeToString(EasyGmUtils.signSm3WithSm2(messageByte, chnlSecretByte, prvkey)); + } + + /** + * sm2验签 + * + * @param msg sm4解密后报文 + * @param source 原始响应报文 + * @param signData 签名串 + * @param sm4key 渠道密钥 + * @param pubKey 平台公钥 + * @return 验证是否通过 boolean + * @throws Exception + */ + public static boolean verify(String msg, String source, String signData, String sm4key, String pubKey) { + byte[] msgByte; + try { + JSONObject jsonObject = JSON.parseObject(msg); + JSONObject jsonObjects = JSON.parseObject(source); + jsonObjects.remove("signData"); + jsonObjects.remove("encData"); + jsonObjects.put("data", jsonObject); + removeEmpty(jsonObject); + String str = SignUtil.getSignText(jsonObjects, sm4key); + msgByte = str.getBytes("UTF-8"); + } catch (Exception e) { + msgByte = msg.getBytes(); + } + byte[] signatureByte = Base64.getDecoder().decode(signData), + chnlSecretByte = sm4key.getBytes(), + pubKeyByte = Base64.getDecoder().decode(pubKey); + return EasyGmUtils.verifySm3WithSm2(msgByte, chnlSecretByte, signatureByte, pubKeyByte); + } + + + /** + * sm4加密 + * + * @param chnlId 渠道id + * @param sm4key 渠道sm4密钥 + * @param message 待加密报文 + * @return 加密后的报文内容 String + * @throws Exception + */ + public static String sm4Encrypt(String chnlId, String sm4key, String message) throws Exception { + // 用appId加密appSecret获取新秘钥 + byte[] appSecretEncData = EasyGmUtils.sm4Encrypt(chnlId.substring(0, 16).getBytes("UTF-8"), sm4key.getBytes("UTF-8")); + // 新秘钥串 + byte[] secKey = Hex.toHexString(appSecretEncData).toUpperCase().substring(0, 16).getBytes("UTF-8"); + // 加密数据 + return Hex.toHexString(EasyGmUtils.sm4Encrypt(secKey, message.getBytes("UTF-8"))).toUpperCase(); + } + + /** + * sm4解密 + * + * @param chnlId 渠道id + * @param sm4key 渠道sm4密钥 + * @param message 待解密报文 + * @return 解密后的报文 String + * @throws Exception + */ + public static String sm4Decrypt(String chnlId, String sm4key, String message) throws Exception { + // 生产解密key + byte[] appSecretEncDataDecode = EasyGmUtils.sm4Encrypt(chnlId.substring(0, 16).getBytes("UTF-8"), sm4key.getBytes("UTF-8")); + byte[] secKeyDecode = Hex.toHexString(appSecretEncDataDecode).toUpperCase().substring(0, 16).getBytes("UTF-8"); + return new String(EasyGmUtils.sm4Decrypt(secKeyDecode, Hex.decode(message))); + } + + private final static String version = "1.0.0"; + private final static String encType = "sm4"; + private final static String signType = "sm2"; + + /** + * 创建请求报文 + * + * @param chnlId 渠道id + * @param encData 加密的报文 + * @param signData 签名的报文 + * @param transType 请求接口名 + * @return + */ + public static JSONObject buildMsg(String chnlId, String encData, String signData, String transType) { + JSONObject jsonObject = new JSONObject(); + jsonObject.put("appId", chnlId); + jsonObject.put("encData", encData); + jsonObject.put("encType", encType); + jsonObject.put("signData", signData); + jsonObject.put("signType", signType); + jsonObject.put("timestamp", System.currentTimeMillis()); + jsonObject.put("transType", transType); + jsonObject.put("version", version); + return jsonObject; + } + + /** + * 创建和加密请求报文 + * + * @param chnlId 渠道id + * @param sm4key 渠道sm4密钥 + * @param prvkey 渠道私钥 + * @param transType 请求接口名 + * @param body 原始未加密的请求报文体 + * @return + * @throws Exception + */ + public static JSONObject encryptMsg(String chnlId, String sm4key, String prvkey, String transType, JSONObject body) throws Exception { + JSONObject jsonObject = new JSONObject(); + jsonObject.put("appId", chnlId); + jsonObject.put("encType", encType); + jsonObject.put("data", body); + jsonObject.put("signType", signType); + jsonObject.put("timestamp", System.currentTimeMillis()); + jsonObject.put("transType", transType); + jsonObject.put("version", version); + // 加密后的报文 + String encData = sm4Encrypt(chnlId, sm4key, body.toJSONString()); + // 签名 + String signData = signature(jsonObject.toJSONString(), sm4key, prvkey); + jsonObject.fluentRemove("data"); + jsonObject.put("encData", encData); + jsonObject.put("signData", signData); + return jsonObject; + } + + /** + * 解密报文 + * + * @param jsonObject 医保电子凭证响应的原始加密报文 + * @param sm4key 渠道sm4密钥 + * @param pubKey 平台公钥 + * @param chnlIdSrc 渠道id(兼容旧中台报文返回参数无渠道id参数情况) + * @return + * @throws Exception + */ + public static String decryptMsg(JSONObject jsonObject, String sm4key, String pubKey, String chnlIdSrc) throws Exception { + String chnlId = (String) jsonObject.get("appId"); + chnlId = StringUtil.isEmpty(chnlId) ? chnlIdSrc : chnlId; + String msg = (String) jsonObject.get("encData"); + String message = (String) jsonObject.get("message"); + String code = (String) jsonObject.get("code"); + if (!"0".equals(code)) { + throw new RuntimeException(message); + } + // 解密 + String msgS = sm4Decrypt(chnlId, sm4key, msg); + // 验签 + String signData = (String) jsonObject.get("signData"); + boolean flag = verify(msgS, jsonObject.toJSONString(), signData, sm4key, pubKey); + if (!flag) { + throw new RuntimeException("验签失败!!!"); + } + return msgS; + } + + /** + * 移除json中空值的键值对 + * + * @param jsonObject + */ + private static void removeEmpty(JSONObject jsonObject) { + Iterator> it = jsonObject.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry entry = it.next(); + Object value = entry.getValue(); + if (value instanceof JSONArray) { + JSONArray jsonArray = (JSONArray) value; + // 数组长度为0时将其处理,防止Gson转换异常 + if (jsonArray.size() == 0) { + it.remove(); + } else { + for (Object o : jsonArray) { + JSONObject asJsonObject = (JSONObject) o; + removeEmpty(asJsonObject); + } + } + } + if (value instanceof JSONObject) { + JSONObject asJsonObject = (JSONObject) value; + removeEmpty(asJsonObject); + } + if (value == null) { + it.remove(); + } + if (value instanceof String && StringUtil.isEmpty(value)) { + it.remove(); + } + } + } + + public static void main(String[] args) throws Exception { + + /** + * 生成sm2公私钥 + */ + // System.out.println(SM2Util.getKeyPairStr()); + + + /** + * 渠道id + */ + String chnlId = ""; + /** + * 渠道私钥 + */ + String prvkey = "TcuVE2p/="; + /** + * 渠道密钥 + */ + String sm4key = ""; + /** + * 平台公钥 + */ + String pubKey = "+++Vvnrxdp8CnkYFXBdu6c="; + + /** + * 报文体 + */ + JSONObject body = new JSONObject(); + body.put("appUserId", "ohNH9sgKsmJC3tR_spm9jcQx_bh"); + body.put("appId", chnlId); + body.put("idNo", ""); + body.put("idType", "01"); + body.put("userName", ""); + body.put("authCode", "111111"); + body.put("redirectURL", "www.baidu.com"); + body.put("phone", "18050860136"); + body.put("certificateStatus", "asdas"); + + + JSONObject s1 = encryptMsg(chnlId, sm4key, prvkey, "ec.gen.link", body); + System.out.println("加密后的报文:" + s1); + + String s2 = decryptMsg(s1, sm4key, pubKey, chnlId); + System.out.println("解密后的报文:" + s2); + + } + +} diff --git a/src/main/java/com/dpkj/modules/chs/ynchs/utils/SignUtil.java b/src/main/java/com/dpkj/modules/chs/ynchs/utils/SignUtil.java new file mode 100644 index 0000000..05529da --- /dev/null +++ b/src/main/java/com/dpkj/modules/chs/ynchs/utils/SignUtil.java @@ -0,0 +1,186 @@ +package com.dpkj.modules.chs.ynchs.utils; + +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import jodd.util.StringUtil; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.TreeMap; + + +public class SignUtil { + private static List ignoreSign = new ArrayList(); + + public static String getSignText(JSONObject jsonObject, String appSecret) { + Map signMap = new TreeMap(); + Set> entrys = jsonObject.entrySet(); + Iterator var4 = entrys.iterator(); + + + while (var4.hasNext()) { + Map.Entry entry = (Map.Entry) var4.next(); + if (!Optional.ofNullable(entry.getValue()).isPresent() && !ignoreSign.contains(entry.getKey())) { + signMap.put(entry.getKey(), getValue(entry.getValue())); + } + } + + ArrayList list = new ArrayList(); + Iterator var10 = signMap.entrySet().iterator(); + + while (var10.hasNext()) { + Map.Entry entry = (Map.Entry) var10.next(); + if (StringUtil.isNotEmpty(getObjString(entry.getValue()))) { + list.add((String) entry.getKey() + "=" + (String) entry.getValue() + "&"); + } + } + + int size = list.size(); + String[] arrayToSort = (String[]) list.toArray(new String[size]); + Arrays.sort(arrayToSort, String.CASE_INSENSITIVE_ORDER); + StringBuilder sb = new StringBuilder(); + + for (int i = 0; i < size; ++i) { + sb.append(arrayToSort[i]); + } + + String signText = sb.append("key=").append(appSecret).toString(); + return signText; + } + + public static String getObjString(Object object) { + return object == null ? "" : (String) object; + } + + private static String getValue(Object value) { + return value instanceof String ? getObjString(value) : treeJsonParam(value); + } + + private static String treeJsonParam(Object value) { + String jsonParam = null; + if (value instanceof Map) { + Map treeNestedMap = new TreeMap(); + Map nestedMap = (Map) value; + Iterator var4 = nestedMap.entrySet().iterator(); + + while (var4.hasNext()) { + Map.Entry nestedEntry = (Map.Entry) var4.next(); + treeNestedMap.put((String) nestedEntry.getKey(), nestedEntry.getValue()); + } + + jsonParam = JSONObject.toJSONString(treeParams(treeNestedMap)); + } else if (value instanceof ArrayList) { + ArrayList ar = (ArrayList) value; + jsonParam = JSONObject.toJSONString(treeList(ar)); + } else if (value instanceof JSONArray) { + JSONArray jarr = (JSONArray) value; + jsonParam = JSONObject.toJSONString(treeJsonArray(jarr)); + } else { + jsonParam = value.toString(); + } + + return jsonParam; + } + + private static Map treeParams(Map params) { + if (params == null) { + return new TreeMap(); + } else { + Map treeParams = new TreeMap(); + Iterator var2 = params.entrySet().iterator(); + + while (true) { + while (var2.hasNext()) { + Map.Entry entry = (Map.Entry) var2.next(); + String key = (String) entry.getKey(); + Object value = entry.getValue(); + if (value instanceof Map) { + Map treeNestedMap = new TreeMap(); + Map nestedMap = (Map) value; + Iterator var8 = nestedMap.entrySet().iterator(); + + while (var8.hasNext()) { + Map.Entry nestedEntry = (Map.Entry) var8.next(); + treeNestedMap.put((String) nestedEntry.getKey(), nestedEntry.getValue()); + } + + treeParams.put(key, treeParams(treeNestedMap)); + } else if (value instanceof ArrayList) { + ArrayList ar = (ArrayList) value; + treeParams.put(key, treeList(ar)); + } else if (value instanceof JSONArray) { + JSONArray ar = (JSONArray) value; + treeParams.put(key, treeJsonArray(ar)); + } else if (!"".equals(value) && value != null) { + treeParams.put(key, value.toString()); + } + } + + return treeParams; + } + } + } + + private static JSONArray treeList(ArrayList list) { + if (list != null && list.size() != 0) { + JSONArray jsonArray = new JSONArray(); + int size = list.size(); + + for (int i = 0; i < size; ++i) { + jsonArray.add(i, list.get(i)); + } + + return treeJsonArray(jsonArray); + } else { + return null; + } + } + + private static JSONArray treeJsonArray(JSONArray jarr) { + if (jarr != null && jarr.size() != 0) { + JSONArray jsonArray = new JSONArray(); + int size = jarr.size(); + + for (int i = 0; i < size; ++i) { + Object value = jarr.get(i); + if (!(value instanceof Map)) { + if (value instanceof ArrayList) { + ArrayList ar = (ArrayList) value; + jsonArray.add(i, treeList(ar)); + } else if (value instanceof JSONArray) { + JSONArray ar = (JSONArray) value; + jsonArray.add(i, treeJsonArray(ar)); + } else if (!"".equals(value)) { + jsonArray.add(i, value.toString()); + } + } else { + Map treeNestedMap = new TreeMap(); + Map nestedMap = (Map) value; + Iterator var7 = nestedMap.entrySet().iterator(); + + while (var7.hasNext()) { + Map.Entry nestedEntry = (Map.Entry) var7.next(); + treeNestedMap.put((String) nestedEntry.getKey(), nestedEntry.getValue()); + } + + jsonArray.add(i, treeParams(treeNestedMap)); + } + } + + return jsonArray; + } else { + return null; + } + } + + static { + ignoreSign.add("signData"); + ignoreSign.add("encData"); + ignoreSign.add("extra"); + } +} diff --git a/src/main/java/com/dpkj/modules/chs/ynchs/utils/StringUtil.java b/src/main/java/com/dpkj/modules/chs/ynchs/utils/StringUtil.java new file mode 100644 index 0000000..b8ecdc8 --- /dev/null +++ b/src/main/java/com/dpkj/modules/chs/ynchs/utils/StringUtil.java @@ -0,0 +1,430 @@ +package com.dpkj.modules.chs.ynchs.utils; + + +import java.io.IOException; +import java.io.InputStream; +import java.math.BigDecimal; +import java.net.URLDecoder; +import java.text.SimpleDateFormat; +import java.util.*; + +/** + * @author: muweng + * @date: 2020/2/20 16:33 + * @description: Life and death can be ignored. + */ +public final class StringUtil { + public static final int XFF00 = 65280; + public static final int XF0 = 240; + public static final int X0F = 15; + public static final int XFF = 255; + public static final int X000000FF = 255; + public static final int X0000FF00 = 65280; + public static final int SIXTEEN = 16; + public static final char[] HEX_CHARS = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; + + public StringUtil() { + } + + public static String getRandomString(int length) { + String base = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + Random random = new Random(); + StringBuffer sb = new StringBuffer(); + + for (int i = 0; i < length; ++i) { + int number = random.nextInt(base.length()); + sb.append(base.charAt(number)); + } + + return sb.toString(); + } + + public static String getRandomNumber(int length) { + String base = "0123456789"; + Random random = new Random(); + StringBuffer sb = new StringBuffer(); + + for (int i = 0; i < length; ++i) { + int number = random.nextInt(base.length()); + sb.append(base.charAt(number)); + } + + return sb.toString(); + } + + public static int[] splitToIntArray(String s, String delim) { + String[] stringValueArray = split(s, delim); + int[] intValueArray = new int[stringValueArray.length]; + + for (int i = 0; i < intValueArray.length; ++i) { + intValueArray[i] = Integer.parseInt(stringValueArray[i]); + } + + return intValueArray; + } + + public static String[] split(String source, String div) { + int arynum = 0; + int div_length = div.length(); + int intIdx; + if (source.compareTo("") != 0) { + if (source.indexOf(div) != -1) { + intIdx = source.indexOf(div); + + for (int var6 = 1; source.indexOf(div, intIdx + div_length) != -1; arynum = var6++) { + intIdx = source.indexOf(div, intIdx + div_length); + } + + arynum += 2; + } else { + arynum = 1; + } + } else { + arynum = 0; + } + + String[] returnStr = new String[arynum]; + if (source.compareTo("") == 0) { + return returnStr; + } else if (source.indexOf(div) == -1) { + returnStr[0] = source.substring(0, source.length()); + return returnStr; + } else { + intIdx = source.indexOf(div); + returnStr[0] = source.substring(0, intIdx); + + int intCount; + for (intCount = 1; source.indexOf(div, intIdx + div_length) != -1; ++intCount) { + int intIdex = source.indexOf(div, intIdx + div_length); + returnStr[intCount] = source.substring(intIdx + div_length, intIdex); + intIdx = source.indexOf(div, intIdx + div_length); + } + + returnStr[intCount] = source.substring(intIdx + div_length, source.length()); + return returnStr; + } + } + + public static int doNullInt(String srcInt) { + return srcInt != null && !"".equals(srcInt) ? Integer.parseInt(srcInt) : 0; + } + + public static int doNullInt(Object obj) { + String srcInt = doNullStr(obj); + return srcInt != null && !"".equals(srcInt) ? Integer.parseInt(srcInt) : 0; + } + + public static long doNullLong(String srcInt) { + return srcInt != null && !"".equals(srcInt) ? Long.parseLong(srcInt) : 0L; + } + + public static long doNullLong(Object obj) { + String srcInt = doNullStr(obj); + return srcInt != null && !"".equals(srcInt) ? Long.parseLong(srcInt) : 0L; + } + + public static String doNullStr(Object obj) { + String str = ""; + if (obj != null) { + str = String.valueOf(obj); + if (str.equals("null")) { + str = ""; + } + } + + return str; + } + + public static Integer doNullInteger(Object obj) { + String str = doNullStr(obj); + if (isEmpty(str)) { + str = "0"; + } else { + int i = str.indexOf("."); + if (i > 0) { + str = str.substring(0, i); + } + } + + return Integer.valueOf(str); + } + + public static boolean isEmpty(String[] string) { + return string == null || string.length == 0; + } + + public static boolean isEmpty(String string) { + return string == null || "".equals(string.trim()) || "null".equals(string.trim()); + } + + public static boolean isNotEmpty(String string) { + return !isEmpty(string); + } + + public static boolean isEmpty(Object o) { + return o == null || "".equals(o); + } + + public static String padRight(String value, int totalWidth, char paddingChar) { + String temp = value; + if (value.length() > totalWidth) { + return value; + } else { + while (temp.length() < totalWidth) { + temp = temp + paddingChar; + } + + return temp; + } + } + + public static String padLeft(String value, int totalWidth, char paddingChar) { + String temp = value; + if (value.length() > totalWidth) { + return value; + } else { + while (temp.length() < totalWidth) { + temp = paddingChar + temp; + } + + return temp; + } + } + + public static String reTrimByString(String value) { + String reValue; + if (value != null && !value.equals("")) { + reValue = value.trim(); + } else { + reValue = ""; + } + + return reValue; + } + + public static String reTrimByObject(Object obj) { + String reValue; + if (obj != null && !obj.equals("")) { + reValue = String.valueOf(obj).trim(); + } else { + reValue = ""; + } + + return reValue; + } + + public static int indexOfStringArray(String[] strArr, String str) { + int index = -1; + if (strArr != null && str != null) { + for (int i = 0; i < strArr.length; ++i) { + if (str.equals(strArr[i])) { + index = i; + break; + } + } + } + + return index; + } + + public static String replaceFirst(String whole, String strold, String strnew) { + if (whole.indexOf(strold) > -1 && strnew != null) { + String whole_one = whole.substring(0, whole.indexOf(strold)); + String whole_two = whole.substring(whole.indexOf(strold) + strold.length()); + whole = whole_one + strnew + whole_two; + } + + return whole; + } + + public static Long[] convertionToLong(String[] strs) { + Long[] longs = null; + if (!isEmpty(strs)) { + longs = new Long[strs.length]; + + for (int i = 0; i < strs.length; ++i) { + String str = strs[i]; + long thelong = Long.valueOf(str).longValue(); + longs[i] = thelong; + } + } + + return longs; + } + + public static Long[] convertionToLongArr(String strs, String splitChar) { + if (isEmpty(splitChar)) { + splitChar = ","; + } + + Long[] result = null; + if (!isEmpty(strs)) { + String[] ids = strs.split(splitChar); + result = new Long[ids.length]; + + for (int i = 0; i < ids.length; ++i) { + result[i] = new Long(ids[i]); + } + } + + return result; + } + + public static String[] decodeStringToArray(String str, String div) { + ArrayList array = new ArrayList(); + StringTokenizer fenxi = new StringTokenizer(str, div); + + while (fenxi.hasMoreTokens()) { + String s1 = fenxi.nextToken(); + array.add(s1); + } + + String[] result = new String[array.size()]; + + for (int i = 0; i < result.length; ++i) { + result[i] = (String) array.get(i); + } + + return result; + } + + public static String convertionLongToString(Long[] l, String splitChar) { + String result = null; + if (l != null) { + result = Arrays.toString(l); + result = result.substring(1, result.length() - 1); + if (!isEmpty(splitChar)) { + result = result.replaceAll(",", splitChar); + } + } + + return result; + } + + public static String convertionObjectArrayToStr(Object[] strings, String regx) { + String result = null; + if (regx != null) { + result = Arrays.toString(strings); + result = result.substring(1, result.length() - 1); + if (!isEmpty(regx)) { + result = result.replaceAll(",", regx); + } + } + + return result; + } + + public static String charEncoding(String str) { + try { + str = URLDecoder.decode(str, "UTF-8"); + } catch (Exception var2) { + str = null; + } + + return str; + } + + public static String getStrTransMean(String str, String sregex, String sreplace) { + if (!isEmpty(str)) { + str = str.replaceAll(sregex, sreplace); + } + + return str; + } + + public static String replaceSpecialChar(String s) { + return s.replaceAll("/|\\\\|\\$|#|&|%|\\*|\\^|;|,|<|>|&|'|\"", ""); + } + + public static String replaceSpecialCode(String s) { + return null != s && !"".equals(s) ? s.replaceAll("<|>|\"|%|;|\\(|\\)|&|'|\\+|\\\\", "") : s; + } + + public static String getMapValue(Map map) { + StringBuffer str = new StringBuffer(); + Iterator it = map.values().iterator(); + + while (it.hasNext()) { + String val = String.valueOf(it.next()); + str.append(val); + } + + return str.toString(); + } + + public static String bSubstring(String s, int length) { + try { + byte[] bytes = s.getBytes("Unicode"); + int n = 0; + + int i; + for (i = 2; i < bytes.length && n < length; ++i) { + if (i % 2 == 1) { + ++n; + } else if (bytes[i] != 0) { + ++n; + } + } + + if (i % 2 == 1) { + if (bytes[i - 1] != 0) { + --i; + } else { + ++i; + } + } + + return new String(bytes, 0, i, "Unicode"); + } catch (Exception var5) { + return new String(""); + } + } + + public static String getString(Object o) { + return o == null ? "" : o.toString(); + } + + public static Properties getProperties(String filename) { + Properties properties = new Properties(); + InputStream in = null; + in = StringUtil.class.getClassLoader().getResourceAsStream(filename); + + try { + properties.load(in); + return properties; + } catch (IOException var4) { + return properties; + } + } + + public static String dateTostr(Date date, String format) throws Exception { + SimpleDateFormat dateFormat = new SimpleDateFormat(format); + String dateStr = null; + dateStr = dateFormat.format(date); + return dateStr; + } + + public static String iso8859ToUTF8(Object obj) { + try { + return obj == null ? "" : new String(obj.toString().getBytes("UTF-8"), "iso-8859-1"); + } catch (Exception var2) { + return ""; + } + } + + public static String utf8ToIso8859(Object obj) { + try { + return obj == null ? "" : new String(obj.toString().getBytes("iso-8859-1"), "UTF-8"); + } catch (Exception var2) { + return ""; + } + } + + public static BigDecimal getStrBigDecimal(String str) { + return isEmpty(str) ? null : new BigDecimal(str); + } + + public static String cutString(String str, int size) { + return isNotEmpty(str) && str.length() > 100 ? str.substring(0, 100) : str; + } +} diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index bcf2c3d..067a24c 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -49,18 +49,18 @@ dpkj: # 医保机构编码 org-id: H53082800070 # 收款员-编号 - operator-id: 0001 + operator-id: 2998 # 收款员-姓名 - operator-name: admin + operator-name: 银医自助缴费 # 科室-编号 - office-id: 00031 + office-id: 71 # 科室-名称 - office-name: 门诊 + office-name: 财务科 # 云南省级医保配置 yn: # 省局医保接口地址 - # url: http://ldjk.yn.hsip.gov.cn/eapdomain/org/local/api/hos/uldFeeInfo - url: http://ldjk.yn.hsip.gov.cn/eapdomain/callService + # url: http://10.114.177.55:8080 + url: http://ldjk.yn.hsip.gov.cn # 医保机构编码 fixme-dins-code: H53082800070 # 医保机构名称 @@ -68,9 +68,22 @@ dpkj: # 服务商统一社会信用代码 info-sys-code: 9150000020285539XU # 服务商ID码 + # info-sys-sign: neu0fe98564e42458f9c86377e10e20c info-sys-sign: 7f6fa92af64a403eb871a3c800cb6946 # 城市编码 city-code: 530800 + # 应用ID + appid: 1IMPBSBC50OO4460C80A00001C356F13 + # 应用密钥 + app-key: 1IMPBSBC50OP4460C80A00004D665784 + # 应用名称 + app-name: 澜沧县中医医院手机自助平台 + # 渠道私钥 + channel-private-key: ALwGHGDcx/UEQiOYHkkym/KCjpDZGfj7AVRV3sxewJX5 + # 渠道公钥 + channel-public-key: BIAUk7awZVtRnd6GXqHTvTak2r3Wg6P7jWsKbBNkbSlephiS1ASWt2yFwpyWxMsqtJ59MDebkfLLDQdB/kfYxEY= + # 平台公钥 + platform-public-key: BPhYoS7Bjo3MRx7I4dbtpa/dR+Na8kMY8F8yzEmlWRQjICAp0tYyfiqPt50N3NVFEijz2DJ0QY9FLCFDEdhnPvY= file: # 文件保存地址 diff --git a/src/main/resources/application-pro.yml b/src/main/resources/application-pro.yml index ff3c00d..cfcae7f 100644 --- a/src/main/resources/application-pro.yml +++ b/src/main/resources/application-pro.yml @@ -48,13 +48,17 @@ dpkj: # 云南省级医保配置 yn: # 省局医保接口地址 - url: http://ldjk.yn.hsip.gov.cn/eapdomain/org/local/api/hos/uldFeeInfo + url: http://ldjk.yn.hsip.gov.cn # 医保机构编码 - fixmedins-code: H53082800070 + fixme-dins-code: H53082800070 + # 医保机构名称 + fixme-dins-name: 澜沧拉祜族自治县中医医院 # 服务商统一社会信用代码 - infosyscode: 9150000020285539XU + info-sys-code: 9150000020285539XU # 服务商ID码 - infosyssign: 7f6fa92af64a403eb871a3c800cb6946 + info-sys-sign: 7f6fa92af64a403eb871a3c800cb6946 + # 城市编码 + city-code: 530800 file: # 文件保存地址 path: D:\Project\Express\upload