CaServer.java 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703
  1. package thyyxxk.webserver.service.ca;
  2. import cn.hutool.core.codec.Base64;
  3. import cn.hutool.core.io.FileUtil;
  4. import cn.hutool.core.util.IdUtil;
  5. import cn.hutool.core.util.IdcardUtil;
  6. import cn.hutool.core.util.StrUtil;
  7. import cn.hutool.core.util.URLUtil;
  8. import cn.hutool.crypto.asymmetric.KeyType;
  9. import cn.hutool.crypto.asymmetric.RSA;
  10. import cn.hutool.json.JSONArray;
  11. import cn.hutool.json.JSONObject;
  12. import cn.hutool.json.JSONUtil;
  13. import com.dtflys.forest.Forest;
  14. import com.dtflys.forest.http.ForestRequest;
  15. import com.dtflys.forest.logging.LogConfiguration;
  16. import com.dtflys.forest.utils.ForestDataType;
  17. import lombok.*;
  18. import lombok.extern.slf4j.Slf4j;
  19. import org.jetbrains.annotations.NotNull;
  20. import org.springframework.stereotype.Service;
  21. import thyyxxk.webserver.config.Assertion;
  22. import thyyxxk.webserver.config.envionment.Archive;
  23. import thyyxxk.webserver.config.envionment.CaData;
  24. import thyyxxk.webserver.config.exception.BizException;
  25. import thyyxxk.webserver.config.exception.ExceptionEnum;
  26. import thyyxxk.webserver.constants.YesOrNo;
  27. import thyyxxk.webserver.dao.his.ca.EmrCaSignDao;
  28. import thyyxxk.webserver.dao.his.zhuyuanyisheng.emr.EmrPatientDao;
  29. import thyyxxk.webserver.entity.ResultVo;
  30. import thyyxxk.webserver.entity.ca.*;
  31. import thyyxxk.webserver.entity.login.UserInfo;
  32. import thyyxxk.webserver.entity.zhuyuanyisheng.emr.EmrPatientData;
  33. import thyyxxk.webserver.service.RedisServer;
  34. import thyyxxk.webserver.service.redislike.RedisLikeService;
  35. import thyyxxk.webserver.service.zhuyuanyisheng.emr.EmrServer;
  36. import thyyxxk.webserver.utils.ResultVoUtil;
  37. import java.io.UnsupportedEncodingException;
  38. import java.net.URLDecoder;
  39. import java.nio.charset.StandardCharsets;
  40. import java.util.ArrayList;
  41. import java.util.List;
  42. import java.util.Objects;
  43. @Slf4j
  44. @Service
  45. public class CaServer implements Assertion {
  46. private final CaData caData;
  47. private final String SUCCRSS = "success";
  48. private final RedisLikeService redisLikeService;
  49. private final EmrServer emrServer;
  50. private final EmrPatientDao dao;
  51. private final RedisServer redisServer;
  52. private final EmrCaSignDao emrCaSignDao;
  53. private final EmrPatientDao emrPatientDao;
  54. private final Archive archive;
  55. private final static String privateKey = "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAKAc+jzqVKHsjNdyuDGL7u6zZR55j87l+0GGT4/yEV+K8ZtL1GoIgOD6vv6eEg2iOwl6Wh6hq8XR4cIyzVyvMZ35dIw3oGN89ObtuCha7gLOmWYUIGVnkUBkLFeIdwe4F8bq1q15o7azsTVekSyNJf/wnxWMp8ibtvO41T8DIzZPAgMBAAECgYAeeLVVD4Gw2VyKij4sy8Var1CQxrDMPu+c7ktJCVguFzrZA8r2rQyeBIqB2aJ07smOibcJ+lm/Ca0N8O4tc/gcm4g/JsjBALFUmqyCkLQpRyIbz8V9uAmrLcchT74GS727Uv4KVTmQNDZTPIDdSZWSJ8S+WKmj8q3wUF1HLF1eXQJBAOC7D6xIB2XNS6Voa7C7m8aeg53lfcDUVJ6m0YFdvMbsU2cnlxnCSX1OsghFuVQLKcGeN+aXiTEileb3pAl2RC0CQQC2ZDtDb+1OmLmRNugp29Pmm8MM8KpktfqiBFi92Q747XwEeMgLi3qetq1wDT4dgvyURs2McsbF7diVtFv7DMXrAkAoYzXj3mYF86k+ps+DyZOrVF2PCOlauE4k3RIVz8TXcy1iAolzRalzbastNWqjIgZ1F3wwYtdzDyYlhifi03BZAkEArkgidPMbwDGhiAf+WhkrZz1JaTECsM9PGcergGVLsENFcQR0qstxtPz7x4lv5EVI0urA+Man93OptIsuJTr0VwJAd86EbHyOn5Lah7ptPGsmsqkrdMZcJ3chRj6pDtvb0A2lhaJfDRxmAiWCnviPhTApH6/1WRzBqc1ZTymyRTF9kA==";
  56. private final static String publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCgHPo86lSh7IzXcrgxi+7us2UeeY/O5ftBhk+P8hFfivGbS9RqCIDg+r7+nhINojsJeloeoavF0eHCMs1crzGd+XSMN6BjfPTm7bgoWu4CzplmFCBlZ5FAZCxXiHcHuBfG6tateaO2s7E1XpEsjSX/8J8VjKfIm7bzuNU/AyM2TwIDAQAB";
  57. RSA idCardRsa = new RSA(privateKey, publicKey);
  58. private String getRedisKey(String documentId) {
  59. return "ca-emr-" + documentId;
  60. }
  61. public CaServer(CaData caData, RedisLikeService redisLikeService, EmrServer emrServer, EmrPatientDao dao, RedisServer redisServer, EmrCaSignDao emrCaSignDao, EmrPatientDao emrPatientDao, Archive archive) {
  62. this.caData = caData;
  63. this.redisLikeService = redisLikeService;
  64. this.emrServer = emrServer;
  65. this.dao = dao;
  66. this.redisServer = redisServer;
  67. this.emrCaSignDao = emrCaSignDao;
  68. this.emrPatientDao = emrPatientDao;
  69. this.archive = archive;
  70. }
  71. public List<CaReturn.CaData> sendBatchByCode(CaSignClass.Send send) {
  72. List<CaReturn.CaData> returnValue = new ArrayList<>();
  73. for (int i = 0; i < send.getCount(); i++) {
  74. returnValue.add(sendByCode(send));
  75. }
  76. return returnValue;
  77. }
  78. public CaReturn.CaData sendByCode(CaSignClass.Send send) {
  79. UserInfo userInfoByCode = redisLikeService.getUserInfoByCode(send.getId());
  80. send.setBizSn(IdUtil.simpleUUID());
  81. String msg = CaUtils.GetSHA256FormString(send.getMsg());
  82. CaReturn push = push(send, msg);
  83. signatureAuthentication(msg, push);
  84. String timeStamp = generateTimestampByInData(msg);
  85. push.getData().setTimeStamp(timeStamp);
  86. push.getData().setBizSn(send.getBizSn());
  87. push.getData().setSignature("/doctorSignatureImage/" + send.getId() + ".png");
  88. push.getData().setName(userInfoByCode.getName());
  89. push.getData().setMsg(msg);
  90. push.getData().setCode(send.getId());
  91. return push.getData();
  92. }
  93. /**
  94. * 推送用户签名
  95. *
  96. * @param send
  97. * @return 返回值
  98. */
  99. private CaReturn push(CaSignClass.Send send, String msg) {
  100. CaData.MobileApp app = caData.getMobileApp();
  101. CaSendParams params = CaSendParams
  102. .builder()
  103. .appId(app.getAppId())
  104. .id(send.getId())
  105. .bizSn(send.getBizSn())
  106. .msg(msg)
  107. .msgWrapper("0")
  108. .desc(URLUtil.encode(send.getDesc()))
  109. .url(URLUtil.encode("http://127.0.0.1"))
  110. .mode("redirect")
  111. .sign(CaUtils.getSign(app.getPrivatekey(), msg))
  112. .build();
  113. String execute = Forest
  114. .post(app.getUrl() + "/v1/push/sign")
  115. .bodyType(ForestDataType.FORM)
  116. .addBody(params).execute(String.class);
  117. String decode = URLUtil.decode(execute);
  118. log.info("返回数据:{}", decode);
  119. CaReturn caReturn = JSONUtil.toBean(decode, CaReturn.class);
  120. if (!SUCCRSS.equals(caReturn.getRet())) {
  121. throw new BizException(ExceptionEnum.LOGICAL_ERROR, "签名失败。" + caReturn.getMsg());
  122. }
  123. if (Integer.valueOf(1).equals(caReturn.getData().getIsTrust())) {
  124. throw new BizException(ExceptionEnum.LOGICAL_ERROR, "签名失败,请用户打开免密签名。");
  125. }
  126. if (Integer.valueOf(2).equals(caReturn.getData().getIsTrust())) {
  127. return caReturn;
  128. }
  129. throw new BizException(ExceptionEnum.LOGICAL_ERROR, "签名失败。" + caReturn.getMsg());
  130. }
  131. /**
  132. * 认证签名
  133. *
  134. * @param msg 消息
  135. * @param caReturn
  136. */
  137. private void signatureAuthentication(String msg, CaReturn caReturn) {
  138. CaData.SignAuthentication signData = caData.getSignAuthentication();
  139. CaWSDL caWSDL = new CaWSDL(signData.getUrl());
  140. String replace = getVerifySignDataParams(msg, caReturn, signData);
  141. JSONObject rst = caWSDL.invoke("verifySignData", replace);
  142. log.info("认证签名xml:{}\n认证签名返回值:{}\n", replace, rst.toString());
  143. Integer code = rst.getByPath("Root.RetCode", Integer.class);
  144. if (!YesOrNo.YES.getCode().equals(code)) {
  145. String rstmsg = rst.getByPath("Root.RetMsg", String.class);
  146. throw new BizException(ExceptionEnum.LOGICAL_ERROR, rstmsg);
  147. }
  148. }
  149. private static @NotNull String getVerifySignDataParams(String msg, CaReturn caReturn, CaData.SignAuthentication signData) {
  150. String request = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n" +
  151. "<Root>\n" +
  152. "\t<AppCode>${AppCode}</AppCode>\n" +
  153. "\t<AppPWD>${AppPWD}</AppPWD>\n" +
  154. "\t<Request>\n" +
  155. "\t\t<Cert>${Cert}</Cert>\n" +
  156. "\t\t<SignAlg>${SignAlg}</SignAlg>\n" +
  157. "\t\t<InData>${InData}</InData>\n" +
  158. "\t\t<SignData>${SignData}</SignData>\n" +
  159. "\t</Request>\n" +
  160. "</Root>";
  161. return request.replace("${AppCode}", signData.getAppCode())
  162. .replace("${AppPWD}", signData.getAppPwd())
  163. .replace("${Cert}", caReturn.getData().getCert())
  164. .replace("${SignAlg}", signData.getSignAig())
  165. .replace("${InData}", msg)
  166. .replace("${SignData}", caReturn.getData().getSignValue());
  167. }
  168. /**
  169. * 认证时间戳并返回
  170. *
  171. * @param msg 数据
  172. * @return
  173. */
  174. private String generateTimestampByInData(String msg) {
  175. String data = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n" +
  176. "<Root>\n" +
  177. "\t<AppCode>${AppCode}</AppCode>\n" +
  178. "\t<AppPWD>${AppPWD}</AppPWD>\n" +
  179. "\t<Request>\n" +
  180. "\t\t<InData>${InData}</InData>\n" +
  181. "<HashAlg>${HashAlg}</HashAlg>\n" +
  182. "<CertReq>${CertReq}</CertReq>\n" +
  183. "\t</Request>\n" +
  184. "</Root>";
  185. CaData.TimestampAuthentication timeData = caData.getTimestampAuthentication();
  186. String request = data.replace("${AppCode}", timeData.getAppCode())
  187. .replace("${AppPWD}", timeData.getAppPwd())
  188. .replace("${InData}", msg)
  189. .replace("${HashAlg}", "SHA256")
  190. .replace("${CertReq}", "true");
  191. CaWSDL caWSDL = new CaWSDL(timeData.getUrl());
  192. JSONObject ret = caWSDL.invoke("generateTimestampByInData", request);
  193. Integer code = ret.getByPath("Root.RetCode", Integer.class);
  194. log.info("认证时间戳xml:{}\n认证时间戳返回值:{}\n", request, ret);
  195. if (code != 1) {
  196. String retmsg = ret.getByPath("Root.RetMsg", String.class);
  197. throw new BizException(ExceptionEnum.LOGICAL_ERROR, retmsg);
  198. }
  199. return ret.getByPath("Root.Response.TimeStamp", String.class);
  200. }
  201. public ResultVo<CaSingBizSnReturnData> getSignInfoByBizSn(String bizSn) {
  202. CaData.MobileApp app = caData.getMobileApp();
  203. ForestRequest<?> post = Forest
  204. .post(app.getUrl() + "/v1/sign/info/" + bizSn)
  205. .setLogConfiguration(new LogConfiguration() {{
  206. setLogEnabled(false);
  207. }})
  208. .bodyType(ForestDataType.JSON);
  209. CaSendParams send = new CaSendParams();
  210. send.setAppId(app.getAppId());
  211. String indata = "appId=" + app.getAppId() + "&bizSn=" + bizSn;
  212. String sign = CaUtils.getSign(app.getPrivatekey(), indata);
  213. send.setSign(sign);
  214. String ret = post.addBody(send).execute(String.class);
  215. CaSingBizSnReturnData execute = JSONUtil.toBean(ret, CaSingBizSnReturnData.class);
  216. if (SUCCRSS.equals(execute.getRet())) {
  217. CaSingBizSnReturnData.SingData data = execute.getData();
  218. try {
  219. data.setSignData(URLDecoder.decode(data.getSignData(), "UTF-8"));
  220. data.setCert(URLDecoder.decode(data.getCert(), "UTF-8"));
  221. data.setSeal(URLDecoder.decode(data.getSeal(), "UTF-8"));
  222. } catch (UnsupportedEncodingException e) {
  223. log.error(e.getMessage());
  224. return ResultVoUtil.success(ExceptionEnum.LOGICAL_ERROR, "解码失败请联系管理员" + bizSn);
  225. }
  226. return ResultVoUtil.success(execute);
  227. }
  228. return ResultVoUtil.success(ExceptionEnum.LOGICAL_ERROR, execute.getMsg());
  229. }
  230. private void verifyMedicalRecordStatus(String documentId, EmrSignType type) {
  231. EmrPatientData emrData = emrPatientDao.selectById(documentId);
  232. // if (emrData == null || emrData.getDelFlag() == 1) {
  233. // throw new BizException(ExceptionEnum.LOGICAL_ERROR, "病历已被删除。");
  234. // }
  235. isTrueErr(emrData.getSignComplete(), "签名流程已完成请勿重复点击");
  236. if (emrData.getSignType() != null && !Objects.equals(type.getCode(), emrData.getSignType())) {
  237. throw new BizException(ExceptionEnum.LOGICAL_ERROR, "请使用上次选择的方式完成CA流程中途不得修改。");
  238. } else {
  239. emrPatientDao.updateById(EmrPatientData.builder()
  240. .emrDocumentId(documentId)
  241. .signType(type.getCode())
  242. .build());
  243. }
  244. }
  245. /**
  246. * 发送签名到移动平板
  247. *
  248. * @param moreEventSign 数据
  249. * @return 暂无
  250. */
  251. public ResultVo<JSONObject> sendMoreEventSign(CaSignClass.MoreEventSign moreEventSign) {
  252. verifyMedicalRecordStatus(moreEventSign.getDocumentId(), EmrSignType.MOVE);
  253. moreEventSign.getData().forEach(item -> {
  254. isBlank(item.getIdCard(), "身份证不能为空。");
  255. isBlank(item.getSignName(), "签名人名字不能为空。");
  256. isBlank(item.getSignType(), "与患者的关系不能为空。");
  257. item.setIdCard(idCardRsa.decryptStr(item.getIdCard(), KeyType.PrivateKey));
  258. });
  259. String redisKey = getRedisKey(moreEventSign.getDocumentId());
  260. String redisData = redisServer.getData(redisKey, () -> null);
  261. if (redisData != null) {
  262. cancelSignature(redisData);
  263. }
  264. redisServer.delData(redisKey);
  265. String uuid = IdUtil.simpleUUID();
  266. JSONObject data = getMoreEventSignData(moreEventSign, uuid);
  267. String execute = Forest.post(caData.getHBoardSign().getUrl() + "/mobile/hBoardSign")
  268. .addBody(data)
  269. .bodyType(ForestDataType.JSON)
  270. .execute(String.class);
  271. JSONObject rst = JSONUtil.parseObj(execute);
  272. String msg = rst.getByPath("ret_msg", String.class);
  273. String decode = URLUtil.decode(msg);
  274. log.info("返回值:{},\n错误信息:{}", execute, decode);
  275. if (rst.getInt("ret_code") == 0) {
  276. redisServer.setData(redisKey, uuid, 30);
  277. return ResultVoUtil.success(ExceptionEnum.SUCCESS_AND_EL_MESSAGE, "发送成功。");
  278. } else {
  279. return ResultVoUtil.fail(ExceptionEnum.LOGICAL_ERROR, "请联系管理员" + decode);
  280. }
  281. }
  282. private JSONObject getMoreEventSignData(CaSignClass.MoreEventSign moreEventSign, String uuid) {
  283. JSONObject ret = new JSONObject();
  284. CaData.HBoardSign app = caData.getHBoardSign();
  285. ret.set("api_key", app.getApiKey());
  286. ret.set("api_secret", app.getApiSecret());
  287. JSONObject data = new JSONObject();
  288. data.set("document_no", uuid);
  289. data.set("pdf", emrServer.getPdfBase(moreEventSign.getDocumentId()));
  290. data.set("return_url", URLUtil.encode(app.getReturnUrl() + "/" + moreEventSign.getDocumentId()));
  291. JSONArray signature = new JSONArray();
  292. for (int i = 0; i < moreEventSign.getData().size(); i++) {
  293. CaSignClass.MoreEventSignData item = moreEventSign.getData().get(i);
  294. JSONObject entries = new JSONObject();
  295. entries.set("type", "keyword");
  296. entries.set("sign_type", URLUtil.encode(item.getSignTypeName()));
  297. entries.set("keyword", new JSONObject() {{
  298. set("page", "0");
  299. set("content", URLUtil.encode(item.getContent()));
  300. }});
  301. JSONObject user_info = new JSONObject() {{
  302. set("name", item.getSignName());
  303. set("idno", item.getIdCard());
  304. set("age", IdcardUtil.getAgeByIdCard(item.getIdCard()));
  305. }};
  306. user_info.set("relation", item.getSignType());
  307. if (StrUtil.isNotBlank(item.getSignOpinion())) {
  308. JSONObject informed_keyword = JSONUtil.createObj()
  309. .set("informed_page", 0)
  310. .set("informed _content", URLUtil.encode(item.getSignOpinion()));
  311. JSONObject informed_stamp = JSONUtil.createObj()
  312. .set("informed_tip", URLUtil.encode("签名意见"))
  313. .set("informed_num", i + 1)
  314. .set("informed_keyword", informed_keyword);
  315. entries.set("informed_stamp", informed_stamp);
  316. }
  317. entries.set("user_info", user_info);
  318. signature.put(entries);
  319. }
  320. // 签署信息
  321. data.set("signature", signature);
  322. data.set("doctor_info", new JSONObject() {{
  323. set("doctor_no", moreEventSign.getSendCode());
  324. }});
  325. data.set("pic_size", new JSONObject() {{
  326. set("width", "60");
  327. set("height", "10");
  328. }});
  329. ret.set("data", data);
  330. return ret;
  331. }
  332. /**
  333. * h5 签名返回一个http连接
  334. *
  335. * @param value
  336. * @return
  337. * @deprecated 没啥用
  338. */
  339. public ResultVo<CaSignClass.H5Return> h5EventSign(CaSignClass.H5EventSign value) {
  340. JSONObject ret = new JSONObject();
  341. String uuid = IdUtil.simpleUUID();
  342. CaData.HBoardSign app = caData.getHBoardSign();
  343. ret.set("api_key", app.getApiKey());
  344. ret.set("api_secret", app.getApiSecret());
  345. ret.set("data", new JSONObject() {{
  346. set("document_no", uuid);
  347. set("pdf", emrServer.getPdfBase(value.getDocumentId()));
  348. set("type", "keyword");
  349. set("keyword", new JSONObject() {{
  350. set("page", "0");
  351. set("content", URLUtil.encode(value.getContent()));
  352. }});
  353. }});
  354. String execute = Forest
  355. .post(caData.getHBoardSign().getUrl() + "/seal/h5EventSign")
  356. .addBody(ret)
  357. .bodyType(ForestDataType.JSON)
  358. .execute(String.class);
  359. return getStringResultVo(execute, uuid, "");
  360. }
  361. @Getter
  362. public enum EmrSignType {
  363. QR(1, "扫码"),
  364. MOVE(2, "移动平板"),
  365. HANDWRITING_BOARD(3, "有线手写板");
  366. private final Integer code;
  367. private final String name;
  368. EmrSignType(int code, String name) {
  369. this.code = code;
  370. this.name = name;
  371. }
  372. }
  373. /**
  374. * CA 扫码签名
  375. *
  376. * @param value
  377. * @return
  378. */
  379. public ResultVo<CaSignClass.H5Return> hBoardSignV2(CaSignClass.H5EventSign value) {
  380. isNoll(value.getSignType(), "请选择和患者的关系");
  381. isBlank(value.getIdCard(), "身份证不能为空。");
  382. isBlank(value.getName(), "姓名不能为空。");
  383. JSONObject ret = new JSONObject();
  384. String caDocumentId = IdUtil.simpleUUID();
  385. verifyMedicalRecordStatus(value.getDocumentId(), EmrSignType.QR);
  386. CaSignClass.H5EventSignRedis redisData = clearHBoardSignV2Data(value);
  387. String idCard = idCardRsa.decryptStr(value.getIdCard(), KeyType.PrivateKey);
  388. ret.set("api_key", caData.getHBoardSign().getApiKey());
  389. ret.set("api_secret", caData.getHBoardSign().getApiSecret());
  390. ret.set("data", new JSONObject() {{
  391. set("document_no", caDocumentId);
  392. set("pdf", redisData.getPdf());
  393. set("sign_way", "face,hand_sign");
  394. set("keyword", new JSONObject() {{
  395. set("page", "0");
  396. set("content", URLUtil.encode(value.getContent()));
  397. }});
  398. set("user_info", new JSONObject() {{
  399. set("name", URLUtil.encode(value.getName()));
  400. set("idno", idCard);
  401. }});
  402. set("collection_source", "face_photos,hand_sign");
  403. if (StrUtil.isNotBlank(value.getSignOpinion())) {
  404. set("informed_stamp", new JSONObject() {{
  405. set("informed_type", "informed_keyword");
  406. set("informed_keyword", new JSONObject() {{
  407. set("informed_page", "0");
  408. set("informed_content", URLUtil.encode(value.getSignOpinion()));
  409. }});
  410. }});
  411. }
  412. }});
  413. String execute = Forest
  414. .post(caData.getHBoardSign().getUrl() + "/v2/seal/hBoardSign")
  415. .addBody(ret)
  416. .bodyType(ForestDataType.JSON)
  417. .execute(String.class);
  418. redisData.setUuid(value.getUuid());
  419. redisServer.setData("ca-emr-" + value.getDocumentId(), redisData, 30);
  420. return getStringResultVo(execute, caDocumentId, value.getUuid());
  421. }
  422. private CaSignClass.H5EventSignRedis clearHBoardSignV2Data(CaSignClass.H5EventSign value) {
  423. CaSignClass.H5EventSignRedis redisData = new CaSignClass.H5EventSignRedis();
  424. String redisKey = getRedisKey(value.getDocumentId());
  425. // 如果 uuid 是空的就是第一次
  426. if (StrUtil.isBlank(value.getUuid())) {
  427. value.setUuid(IdUtil.simpleUUID());
  428. redisServer.delData(redisKey);
  429. redisData.setPdf(emrServer.getPdfBase(value.getDocumentId()));
  430. } else {
  431. redisData = redisServer.getData(redisKey);
  432. if (redisData == null) {
  433. throw new BizException(ExceptionEnum.LOGICAL_ERROR, "签名超时请重新签名。");
  434. }
  435. if (!redisData.getUuid().equals(value.getUuid())) {
  436. throw new BizException(ExceptionEnum.LOGICAL_ERROR, "有其他人正在签署,本次签名失效。");
  437. }
  438. }
  439. return redisData;
  440. }
  441. @NotNull
  442. private ResultVo<CaSignClass.H5Return> getStringResultVo(String execute,
  443. String caDocumentId,
  444. String uuid) {
  445. JSONObject rst = JSONUtil.parseObj(execute);
  446. String msg = URLUtil.decode(rst.getByPath("ret_msg", String.class));
  447. if (0 == rst.getByPath("ret_code", Integer.class)) {
  448. CaSignClass.H5Return build = CaSignClass.H5Return.builder()
  449. .result(rst.getByPath("sign_url", String.class))
  450. .caDocumentId(caDocumentId)
  451. .uuid(uuid)
  452. .build();
  453. return ResultVoUtil.success(build);
  454. }
  455. return ResultVoUtil.fail(ExceptionEnum.NO_DATA_EXIST, "签名失败" + msg);
  456. }
  457. public ResultVo<JSONObject> downloadSealV2(String id, String documentId) {
  458. JSONObject ret = new JSONObject();
  459. ret.set("api_key", caData.getHBoardSign().getApiKey());
  460. ret.set("api_secret", caData.getHBoardSign().getApiSecret());
  461. ret.set("data", new JSONObject() {{
  462. set("document_no", id);
  463. }});
  464. String execute = Forest
  465. .post(caData.getHBoardSign().getUrl() + "/v2/seal/download")
  466. .addBody(ret)
  467. .bodyType(ForestDataType.JSON)
  468. .execute(String.class);
  469. JSONObject rst = JSONUtil.parseObj(execute);
  470. String msg = URLUtil.decode(rst.getByPath("ret_msg", String.class));
  471. if (0 == rst.getByPath("ret_code", Integer.class)) {
  472. return ResultVoUtil.success(rst);
  473. }
  474. return ResultVoUtil.fail(ExceptionEnum.NO_DATA_EXIST, msg);
  475. }
  476. /**
  477. * 判断扫码是否完成以及回写扫码的数据
  478. *
  479. * @return 提示
  480. */
  481. public ResultVo<Boolean> scanCodeVerification(String id, String documentId, String uuid) {
  482. ResultVo<JSONObject> res = downloadSealV2(id, documentId);
  483. if (!res.getCode().equals(ExceptionEnum.SUCCESS.getCode())) {
  484. return ResultVoUtil.fail(ExceptionEnum.LOGICAL_ERROR, res.getMessage());
  485. }
  486. JSONObject rst = res.getData();
  487. String key = getRedisKey(documentId);
  488. CaSignClass.H5EventSignRedis redisData = redisServer.getData(key);
  489. if (redisData == null) {
  490. throw new BizException(ExceptionEnum.LOGICAL_ERROR, "签名错误,文书不存在。");
  491. }
  492. if (!redisData.getUuid().equals(uuid)) {
  493. throw new BizException(ExceptionEnum.LOGICAL_ERROR, "有其他人正在签署,本次签名失效。");
  494. }
  495. redisData.setPdf(rst.getByPath("pdf", String.class));
  496. JSONObject saveData = new JSONObject();
  497. saveData.set("hand_sign", rst.get("hand_sign"));
  498. saveData.set("fingerprint", rst.get("hand_sign"));
  499. saveData.set("informed_img", rst.get("hand_sign"));
  500. saveData.set("face_photo", rst.get("hand_sign"));
  501. saveData.set("biz_sn", id);
  502. if (null == redisData.getData()) {
  503. redisData.setData(new ArrayList<>());
  504. }
  505. redisData.getData().add(saveData);
  506. redisServer.setData(key, redisData, 30);
  507. return ResultVoUtil.success(true);
  508. }
  509. public static void main(String[] args) {
  510. System.out.println(EmrServer.getPdfBase("927936636556216320"));
  511. }
  512. /**
  513. * 扫码流程完成
  514. */
  515. public void completeQrCode(String documentId) {
  516. EmrPatientData data = emrPatientDao.selectById(documentId);
  517. String redisKey = getRedisKey(documentId);
  518. CaSignClass.H5EventSignRedis redisServerData = redisServer.getData(redisKey);
  519. CaSignClass.Upload upload = CaSignClass.Upload.builder()
  520. .file(redisServerData.getPdf())
  521. .fileName(documentId)
  522. .patNo(data.getPatNo())
  523. .times(data.getTimes())
  524. .caData(JSONUtil.toJsonStr(redisServerData))
  525. .build();
  526. String path = uploadBase64(upload);
  527. EmrPatientData build = EmrPatientData
  528. .builder()
  529. .emrDocumentId(documentId)
  530. .signComplete(true)
  531. .archivePath(path)
  532. .build();
  533. emrPatientDao.updateById(build);
  534. redisServer.delData(redisKey);
  535. }
  536. public void cancelSignature(String id) {
  537. JSONObject ret = new JSONObject();
  538. CaData.HBoardSign app = caData.getHBoardSign();
  539. ret.set("api_key", app.getApiKey());
  540. ret.set("api_secret", app.getApiSecret());
  541. ret.set("data", new JSONObject() {{
  542. set("document_no", id);
  543. }});
  544. Forest.post(caData.getHBoardSign().getUrl() + "/v2/seal/cancelSigne")
  545. .addBody(ret)
  546. .bodyType(ForestDataType.JSON)
  547. .execute(JSONObject.class);
  548. }
  549. /**
  550. * 签名回调
  551. *
  552. * @param document 文档id
  553. * @param data 签名数据
  554. * @return
  555. */
  556. public JSONObject hBoardSignReturnUrl(String document, JSONObject data) {
  557. // TODO 完成一定手写板签名的时候,需要保存一下PDF
  558. log.info("回调:{},数据:{}", document, data);
  559. EmrPatientData emrData = emrPatientDao.selectById(document);
  560. String redisKey = getRedisKey(document);
  561. String redisData = redisServer.getData(redisKey, () -> null);
  562. if (emrData == null || redisData == null) {
  563. return R(-1, "病历文档不存在");
  564. }
  565. JSONObject signData = appDownload(redisData);
  566. CaSignClass.Upload build = CaSignClass.Upload
  567. .builder()
  568. .patNo(emrData.getPatNo())
  569. .times(emrData.getTimes())
  570. .file(signData.getStr("pdf"))
  571. .fileName(document)
  572. .caData(signData.getStr("signed"))
  573. .build();
  574. String path = uploadBase64(build);
  575. EmrPatientData update = EmrPatientData.builder()
  576. .emrDocumentId(document)
  577. .signComplete(true)
  578. .archivePath(path)
  579. .build();
  580. redisServer.delData(redisKey);
  581. emrPatientDao.updateById(update);
  582. return R(1, "success");
  583. }
  584. private JSONObject R(Integer code, String msg) {
  585. return new JSONObject() {{
  586. set("ret_code", code);
  587. set("res_msg", msg);
  588. }};
  589. }
  590. public JSONObject appDownload(String bizSn) {
  591. JSONObject data = new JSONObject() {{
  592. set("api_key", caData.getHBoardSign().getApiKey());
  593. set("api_secret", caData.getHBoardSign().getApiSecret());
  594. set("data", new JSONObject() {{
  595. set("document_no", bizSn);
  596. }});
  597. }};
  598. JSONObject execute = Forest
  599. .post(caData.getHBoardSign().getUrl() + "/app/download")
  600. .bodyType(ForestDataType.JSON)
  601. .addBody(data.toJSONString(0))
  602. .execute(JSONObject.class);
  603. execute.set("ret_msg", URLUtil.decode(execute.getStr("ret_msg")));
  604. log.info("返回:{}", execute);
  605. if (execute.getInt("code") != 0) {
  606. throw new BizException(ExceptionEnum.LOGICAL_ERROR, execute.getStr("ret_msg"));
  607. }
  608. return execute;
  609. }
  610. public String uploadBase64(CaSignClass.Upload upload) {
  611. byte[] file = Base64.decode(upload.getFile());
  612. String tmpPath = "/emr" + "/" + upload.getPatNo()
  613. + "/" + upload.getTimes()
  614. + "/" + upload.getFileName();
  615. String pdfPath = tmpPath + ".pdf";
  616. String caPath = "/emr" + "/" + upload.getPatNo()
  617. + "/" + upload.getTimes()
  618. + "/ca/" + upload.getFileName()
  619. + ".txt";
  620. // 写入CA数据
  621. FileUtil.writeString(upload.getCaData(), archive.getPath() + caPath, StandardCharsets.UTF_8);
  622. // 写入pdf
  623. FileUtil.writeBytes(file, archive.getPath() + pdfPath);
  624. return "/archive" + pdfPath;
  625. }
  626. }