WxApiService.java 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419
  1. package thyyxxk.wxservice_server.service;
  2. import com.alibaba.fastjson.JSONObject;
  3. import lombok.extern.slf4j.Slf4j;
  4. import org.apache.http.client.methods.CloseableHttpResponse;
  5. import org.apache.http.client.methods.HttpPost;
  6. import org.apache.http.entity.StringEntity;
  7. import org.apache.http.impl.client.CloseableHttpClient;
  8. import org.apache.http.util.EntityUtils;
  9. import org.dom4j.Document;
  10. import org.dom4j.DocumentException;
  11. import org.dom4j.DocumentHelper;
  12. import org.dom4j.Element;
  13. import org.springframework.beans.factory.annotation.Autowired;
  14. import org.springframework.http.converter.HttpMessageConverter;
  15. import org.springframework.http.converter.StringHttpMessageConverter;
  16. import org.springframework.stereotype.Service;
  17. import org.springframework.web.client.RestTemplate;
  18. import thyyxxk.wxservice_server.config.exception.ExceptionEnum;
  19. import thyyxxk.wxservice_server.constant.OrderType;
  20. import thyyxxk.wxservice_server.constant.QuerySource;
  21. import thyyxxk.wxservice_server.constant.TradeState;
  22. import thyyxxk.wxservice_server.dao.WxApiDao;
  23. import thyyxxk.wxservice_server.entity.ResultVo;
  24. import thyyxxk.wxservice_server.entity.appointment.DoctorInfo;
  25. import thyyxxk.wxservice_server.entity.appointment.WeChatPayParam;
  26. import thyyxxk.wxservice_server.entity.wxapi.JsApiSHA1;
  27. import thyyxxk.wxservice_server.entity.wxapi.GenMzPayQrcodeParam;
  28. import thyyxxk.wxservice_server.entity.wxapi.WxPayOrder;
  29. import thyyxxk.wxservice_server.entity.wxapi.WxPyQrcdPrm;
  30. import thyyxxk.wxservice_server.utils.*;
  31. import java.nio.charset.StandardCharsets;
  32. import java.util.*;
  33. import java.util.concurrent.TimeUnit;
  34. /**
  35. * @author dj
  36. */
  37. @Slf4j
  38. @Service
  39. public class WxApiService {
  40. private final WxApiDao dao;
  41. private final SpringRetryService retryService;
  42. private final SavePayResultService savePayResultService;
  43. @Autowired
  44. public WxApiService(WxApiDao dao, SpringRetryService retryService, SavePayResultService savePayResultService) {
  45. this.dao = dao;
  46. this.retryService = retryService;
  47. this.savePayResultService = savePayResultService;
  48. }
  49. public ResultVo<JsApiSHA1> getJsapiSHA1Sign(JsApiSHA1 data) {
  50. data.setAppId(PropertiesUtil.getProperty("appId"));
  51. data.setTicket(PropertiesUtil.getProperty("ticket"));
  52. data.setNoncestr(SnowFlakeId.instance().nextId());
  53. data.setTimestamp(String.valueOf(System.currentTimeMillis() / 1000));
  54. String toSign = "jsapi_ticket=" + data.getTicket() + "&noncestr=" + data.getNoncestr() +
  55. "&timestamp=" + data.getTimestamp() + "&url=" + data.getUrl();
  56. data.setSignature(WxPaySignUtil.sha1Encode(toSign));
  57. log.info("获取wxJsApi签名:{}", data);
  58. return ResultVoUtil.success(data);
  59. }
  60. public ResultVo<WxPayOrder> createPayOrder(WeChatPayParam param) {
  61. WxPayOrder existOrder = null;
  62. if (param.getOrderType() == OrderType.REGISTRATION.getCode()) {
  63. existOrder = dao.selectSameGhOrder(param.getPatientId(), param.getMzyRequestId());
  64. } else if (param.getOrderType() == OrderType.OUTPATIENT.getCode()) {
  65. existOrder = dao.selectSameMzPayOrder(param.getHisOrdNum());
  66. }
  67. if (null != existOrder) {
  68. if (existOrder.getPayStatus() == TradeState.SUCCESS.getCode()) {
  69. return ResultVoUtil.fail(ExceptionEnum.LOGICAL_ERROR, "您已成功支付过一笔相同金额的订单,请勿重复支付。");
  70. }
  71. if (StringUtil.notBlank(existOrder.getPaySign()) && StringUtil.notBlank(existOrder.getPrepayId())) {
  72. if (existOrder.getPayStatus() == TradeState.NEWORDER.getCode() || existOrder.getPayStatus() == TradeState.NOTPAY.getCode()) {
  73. if (DateUtil.orderValid(existOrder.getCreateDatetime())) {
  74. return ResultVoUtil.success(existOrder);
  75. }
  76. }
  77. if (null == param.getYjReqNo()) {
  78. param.setYjReqNo(existOrder.getYjReqNo());
  79. }
  80. }
  81. }
  82. String appId = PropertiesUtil.getProperty("appId");
  83. String merchantId = PropertiesUtil.getProperty("mchId");
  84. String tradeNo = SnowFlakeId.instance().nextId();
  85. String nonceStr = SnowFlakeId.instance().nextId();
  86. String totalFee = DecimalTool.moneyYuanToFen(param.getTotalFee());
  87. String notifyUrl = "http://staticweb.hnthyy.cn/wxserver/wxPayNotify/notify2";
  88. TreeMap<String, String> map = new TreeMap<>();
  89. map.put("appid", appId);
  90. map.put("mch_id", merchantId);
  91. map.put("device_info", "WEB");
  92. map.put("body", param.getBody());
  93. map.put("trade_type", "JSAPI");
  94. map.put("nonce_str", nonceStr);
  95. map.put("notify_url", notifyUrl);
  96. map.put("out_trade_no", tradeNo);
  97. map.put("total_fee", totalFee);
  98. map.put("spbill_create_ip", param.getClientIp());
  99. map.put("openid", param.getOpenId());
  100. String createOrderSign = WxPaySignUtil.createSign(map);
  101. String xml = "<xml>" +
  102. "<appid>" + appId + "</appid>" +
  103. "<body>" + param.getBody() + "</body>" +
  104. "<device_info>WEB</device_info>" +
  105. "<mch_id>" + merchantId + "</mch_id>" +
  106. "<nonce_str>" + nonceStr + "</nonce_str>" +
  107. "<notify_url>" + notifyUrl + "</notify_url>" +
  108. "<openid>" + param.getOpenId() + "</openid>" +
  109. "<out_trade_no>" + tradeNo + "</out_trade_no>" +
  110. "<total_fee>" + totalFee + "</total_fee>" +
  111. "<spbill_create_ip>" + param.getClientIp() + "</spbill_create_ip>" +
  112. "<trade_type>JSAPI</trade_type>" +
  113. "<sign>" + createOrderSign + "</sign>" +
  114. "</xml>";
  115. String url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
  116. RestTemplate restTemplate = new RestTemplate();
  117. List<HttpMessageConverter<?>> list = restTemplate.getMessageConverters();
  118. for (HttpMessageConverter<?> httpMessageConverter : list) {
  119. if (httpMessageConverter instanceof StringHttpMessageConverter) {
  120. ((StringHttpMessageConverter) httpMessageConverter).setDefaultCharset(StandardCharsets.UTF_8);
  121. break;
  122. }
  123. }
  124. String str = restTemplate.postForObject(url, xml, String.class);
  125. try {
  126. assert str != null;
  127. Document document = DocumentHelper.parseText(str);
  128. Element root = document.getRootElement();
  129. if ("SUCCESS".equals(root.element("return_code").getStringValue())) {
  130. TreeMap<String, String> back = new TreeMap<>();
  131. if (null == root.element("prepay_id")) {
  132. final String message = root.element("err_code_des").getStringValue();
  133. log.info("微信统一下单失败:{}", message);
  134. return ResultVoUtil.fail(ExceptionEnum.LOGICAL_ERROR, message);
  135. }
  136. String prepayId = root.element("prepay_id").getStringValue();
  137. String timeStamp = Long.toString(System.currentTimeMillis());
  138. back.put("appId", PropertiesUtil.getProperty("appId"));
  139. back.put("timeStamp", timeStamp);
  140. back.put("nonceStr", nonceStr);
  141. back.put("package", "prepay_id=" + prepayId);
  142. back.put("signType", "MD5");
  143. String paySign = WxPaySignUtil.createSign(back);
  144. WxPayOrder order = new WxPayOrder();
  145. order.setBody(param.getBody());
  146. order.setOrderType(param.getOrderType());
  147. order.setOpenId(param.getOpenId());
  148. order.setTotalFee(param.getTotalFee());
  149. order.setPatientId(param.getPatientId());
  150. order.setPatientName(dao.selectPatientName(param.getPatientId()));
  151. order.setAppId(appId);
  152. order.setMchId(merchantId);
  153. order.setPrepayId(prepayId);
  154. order.setTimeStamp(timeStamp);
  155. order.setSerialNo(nonceStr);
  156. order.setTradeNo(tradeNo);
  157. order.setCreateOrderSign(createOrderSign);
  158. order.setPaySign(paySign);
  159. order.setSpbillCreateIp(param.getClientIp());
  160. order.setCreateDatetime(new Date());
  161. order.setPayStatus(TradeState.NEWORDER.getCode());
  162. order.setHisOrdNum(param.getHisOrdNum());
  163. order.setMzyRequestId(param.getMzyRequestId());
  164. order.setYjReqNo(param.getYjReqNo());
  165. order.setInpatientNo(param.getInpatientNo());
  166. order.setAdmissTimes(param.getAdmissTimes());
  167. dao.insertNewOrder(order);
  168. log.info("统一下单成功:{}", order);
  169. return ResultVoUtil.success(order);
  170. }
  171. final String message = root.element("return_msg").getStringValue();
  172. log.error("微信统一下单失败:{}", message);
  173. return ResultVoUtil.fail(ExceptionEnum.LOGICAL_ERROR, message);
  174. } catch (DocumentException e) {
  175. e.printStackTrace();
  176. return ResultVoUtil.fail(ExceptionEnum.INTERNAL_SERVER_ERROR, e.getMessage());
  177. }
  178. }
  179. public ResultVo<Object> queryOrderState(String tradeNo, QuerySource source) throws Exception {
  180. while (TradeVectorUtil.tradeNoBeingQuery(tradeNo)) {
  181. log.info("订单号:{} 正在查询状态中,进入等待区。", tradeNo);
  182. TimeUnit.SECONDS.sleep(3);
  183. }
  184. TradeVectorUtil.add(tradeNo);
  185. try {
  186. WxPayOrder order = dao.selectOrderByTradeNo(tradeNo);
  187. OrderType orderType = OrderType.get(order.getOrderType());
  188. int hasSaved = 0;
  189. if (orderType == OrderType.REGISTRATION) {
  190. hasSaved = savePayResultService.queryAppointmentSaveStatus(order.getTradeNo(),
  191. order.getSerialNo(), order.getTotalFee().toPlainString(), order.getPayDatetime());
  192. } else if (orderType == OrderType.OUTPATIENT) {
  193. hasSaved = savePayResultService.queryMzPaySaveStatus(order.getHisOrdNum(), order.getTradeNo(),
  194. order.getSerialNo(), order.getTotalFee().toPlainString(), order.getPayDatetime());
  195. }
  196. if (hasSaved == 1) {
  197. dao.updateSuccessHisStatus(tradeNo);
  198. if (orderType == OrderType.REGISTRATION) {
  199. log.info("订单号:{} 的挂号信息已保存,无需再次查询订单状态。", order.getTradeNo());
  200. return ResultVoUtil.success("保存挂号信息成功。");
  201. } else {
  202. log.info("订单号:{} 的门诊缴费信息已保存,无需再次查询订单状态。", order.getTradeNo());
  203. if (source == QuerySource.INTERFACE) {
  204. String hisOrdNum = order.getHisOrdNum();
  205. if (StringUtil.notBlank(hisOrdNum)) {
  206. String[] hsrdnms = hisOrdNum.split("_");
  207. Integer isCovidExam = dao.selectSelfCovidExamReceiptCount(hsrdnms[0], hsrdnms[1], hsrdnms[2]);
  208. if (null != isCovidExam && isCovidExam > 0) {
  209. order.setBody("新冠肺炎核酸检测");
  210. return ResultVoUtil.success("showBill", order);
  211. }
  212. }
  213. }
  214. return ResultVoUtil.success("保存门诊缴费信息成功。");
  215. }
  216. }
  217. JSONObject obj = retryService.queryOrderStateFromTencent(tradeNo, orderType, source);
  218. String value = obj.getString("trade_state");
  219. TradeState tradeState = TradeState.get(value);
  220. if (tradeState.equals(TradeState.SUCCESS)) {
  221. String successTime = obj.getString("success_time")
  222. .split("\\+")[0].replace("T", " ");
  223. dao.updatePayStatusAndPayTime(tradeNo, tradeState.getCode(), successTime);
  224. switch (orderType) {
  225. case REGISTRATION:
  226. return savePayResultService.saveAppointment(order);
  227. case OUTPATIENT:
  228. return savePayResultService.saveMzChargeInfo(order, source, successTime);
  229. case INPATIENT_PRE_PAY:
  230. return savePayResultService.saveZyYjjInfo(order);
  231. case SELF_HELP_MACHINE:
  232. case INSPECTIONS:
  233. return ResultVoUtil.success("订单已支付。");
  234. default:
  235. return ResultVoUtil.fail(ExceptionEnum.LOGICAL_ERROR, "未识别到的订单类型,请联系服务中心。");
  236. }
  237. }
  238. dao.updatePayStatusAndQueryTimes(tradeNo, tradeState.getCode());
  239. return ResultVoUtil.fail(ExceptionEnum.INTERNAL_SERVER_ERROR, tradeState.getLabel());
  240. } catch (Exception e) {
  241. e.printStackTrace();
  242. return ResultVoUtil.fail(ExceptionEnum.LOGICAL_ERROR, "服务器错误,请联系管理员处理。【订单号:" + tradeNo + "】");
  243. } finally {
  244. TradeVectorUtil.remove(tradeNo);
  245. }
  246. }
  247. public ResultVo<String> generateMzGuideBillPayQrcode(GenMzPayQrcodeParam param) throws Exception {
  248. String outTradeNo = SnowFlakeId.instance().nextId();
  249. JSONObject body = new JSONObject();
  250. body.put("appid", PropertiesUtil.getProperty("appId"));
  251. body.put("mchid", PropertiesUtil.getProperty("mchId"));
  252. body.put("description", "湖南泰和医院-门诊缴费");
  253. body.put("attach", "湖南泰和医院-门诊缴费");
  254. body.put("out_trade_no", outTradeNo);
  255. body.put("notify_url", PropertiesUtil.getProperty("notifyUrl"));
  256. JSONObject cny = new JSONObject();
  257. cny.put("total", param.getTotalAmt());
  258. cny.put("currency", "CNY");
  259. body.put("amount", cny);
  260. String reqdata = JSONObject.toJSONString(body);
  261. log.info("请求门诊指引单二维码:{}", reqdata);
  262. HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/native");
  263. StringEntity entity = new StringEntity(reqdata, StandardCharsets.UTF_8);
  264. entity.setContentEncoding("UTF-8");
  265. entity.setContentType("application/json");
  266. httpPost.setEntity(entity);
  267. httpPost.addHeader("Content-Type", "application/json;charset=UTF-8");
  268. httpPost.addHeader("Accept", "application/json");
  269. //完成签名并执行请求
  270. CloseableHttpClient httpClient = WxHttpUtil.getClosableHttpClient();
  271. CloseableHttpResponse response = httpClient.execute(httpPost);
  272. int statusCode = response.getStatusLine().getStatusCode();
  273. String ret = EntityUtils.toString(response.getEntity());
  274. httpClient.close();
  275. if (statusCode == ExceptionEnum.SUCCESS.getCode()) {
  276. long timesStamp = System.currentTimeMillis() / 1000;
  277. WxPayOrder order = new WxPayOrder();
  278. order.setAppId(PropertiesUtil.getProperty("appId"));
  279. order.setBody("门诊缴费");
  280. order.setOpenId("");
  281. order.setTotalFee(DecimalTool.moneyFenToYuan(param.getTotalAmt()));
  282. order.setPatientId(param.getPatientId());
  283. order.setPatientName(dao.selectPatientName(param.getPatientId()));
  284. order.setMchId(PropertiesUtil.getProperty("mchId"));
  285. order.setTimeStamp(String.valueOf(timesStamp));
  286. order.setTradeNo(outTradeNo);
  287. order.setCreateDatetime(new Date());
  288. order.setPayStatus(TradeState.NOTPAY.getCode());
  289. order.setSerialNo(SnowFlakeId.instance().nextId());
  290. order.setOrderType(OrderType.OUTPATIENT.getCode());
  291. order.setHisOrdNum(param.getHisOrdNum());
  292. dao.insertNewOrder(order);
  293. JSONObject retObj = JSONObject.parseObject(ret);
  294. return ResultVoUtil.success(retObj.getString("code_url"));
  295. }
  296. log.error("请求门诊指引单二维码失败:{}", ret);
  297. return ResultVoUtil.fail(ExceptionEnum.LOGICAL_ERROR, "请求微信支付二维码失败,请联系管理员。");
  298. }
  299. public ResultVo<Map<String, String>> getWxPayQrcode(WxPyQrcdPrm prm) throws Exception {
  300. String patName = dao.selectPatientName(prm.getPatientId());
  301. if (null == patName) {
  302. return ResultVoUtil.fail(ExceptionEnum.NULL_POINTER, "没有找到患者信息,请检查patientId是否正确!");
  303. }
  304. WxPayOrder order = new WxPayOrder();
  305. if (prm.getOrderType() == OrderType.INPATIENT_PRE_PAY.getCode()) {
  306. if (StringUtil.isBlank(prm.getInpatientNo())) {
  307. return ResultVoUtil.fail(ExceptionEnum.NULL_POINTER, "费用类型为住院预交金时,住院号不能为空!");
  308. }
  309. if (null == prm.getAdmissTimes()) {
  310. return ResultVoUtil.fail(ExceptionEnum.NULL_POINTER, "费用类型为住院预交金时,住院次数不能为空!");
  311. }
  312. order.setInpatientNo(prm.getInpatientNo());
  313. order.setAdmissTimes(prm.getAdmissTimes());
  314. }
  315. String outTradeNo = SnowFlakeId.instance().nextId();
  316. JSONObject body = new JSONObject();
  317. body.put("appid", PropertiesUtil.getProperty("appId"));
  318. body.put("mchid", PropertiesUtil.getProperty("mchId"));
  319. body.put("description", prm.getDescription());
  320. body.put("attach", prm.getDescription());
  321. body.put("out_trade_no", outTradeNo);
  322. body.put("notify_url", PropertiesUtil.getProperty("notifyUrl"));
  323. JSONObject cny = new JSONObject();
  324. cny.put("total", prm.getTotalAmt());
  325. cny.put("currency", "CNY");
  326. body.put("amount", cny);
  327. String reqdata = JSONObject.toJSONString(body);
  328. HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/native");
  329. StringEntity entity = new StringEntity(reqdata, StandardCharsets.UTF_8);
  330. entity.setContentEncoding("UTF-8");
  331. entity.setContentType("application/json");
  332. httpPost.setEntity(entity);
  333. httpPost.addHeader("Content-Type", "application/json;charset=UTF-8");
  334. httpPost.addHeader("Accept", "application/json");
  335. CloseableHttpClient httpClient = WxHttpUtil.getClosableHttpClient();
  336. CloseableHttpResponse response = httpClient.execute(httpPost);
  337. int statusCode = response.getStatusLine().getStatusCode();
  338. String ret = EntityUtils.toString(response.getEntity());
  339. httpClient.close();
  340. log.info("请求微信支付二维码:{},结果:{}", prm, ret);
  341. if (statusCode == ExceptionEnum.SUCCESS.getCode()) {
  342. long timesStamp = System.currentTimeMillis() / 1000;
  343. String orderTypeName = OrderType.get(prm.getOrderType()).getLabel();
  344. order.setAppId(PropertiesUtil.getProperty("appId"));
  345. order.setBody(orderTypeName);
  346. order.setOpenId("");
  347. order.setTotalFee(DecimalTool.moneyFenToYuan(prm.getTotalAmt()));
  348. order.setPatientId(prm.getPatientId());
  349. order.setPatientName(patName);
  350. order.setMchId(PropertiesUtil.getProperty("mchId"));
  351. order.setTimeStamp(String.valueOf(timesStamp));
  352. order.setTradeNo(outTradeNo);
  353. order.setCreateDatetime(new Date());
  354. order.setPayStatus(TradeState.NOTPAY.getCode());
  355. order.setSerialNo(SnowFlakeId.instance().nextId());
  356. order.setOrderType(prm.getOrderType());
  357. dao.insertNewOrder(order);
  358. JSONObject retObj = JSONObject.parseObject(ret);
  359. Map<String, String> map = new HashMap<>();
  360. map.put("qrcodeUrl", retObj.getString("code_url"));
  361. map.put("tradeNo", order.getTradeNo());
  362. map.put("serialNo", order.getSerialNo());
  363. return ResultVoUtil.success(map);
  364. }
  365. return ResultVoUtil.fail(ExceptionEnum.LOGICAL_ERROR, "请求微信支付二维码失败,请联系管理员。");
  366. }
  367. public ResultVo<String> closeWxOrder(WxPayOrder order) throws Exception {
  368. Integer payStatus = dao.selectOrderStatus(order.getTradeNo());
  369. if (null == payStatus) {
  370. return ResultVoUtil.fail(ExceptionEnum.NULL_POINTER, "订单不存在,请检查订单号是否正确。");
  371. }
  372. if (payStatus == TradeState.CLOSED.getCode()) {
  373. return ResultVoUtil.success("订单已是关闭状态,请勿重复关闭订单。");
  374. }
  375. String url = "https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/" + order.getTradeNo() + "/close";
  376. JSONObject body = new JSONObject();
  377. body.put("mchid", PropertiesUtil.getProperty("mchId"));
  378. String reqdata = JSONObject.toJSONString(body);
  379. HttpPost httpPost = new HttpPost(url);
  380. StringEntity entity = new StringEntity(reqdata, StandardCharsets.UTF_8);
  381. entity.setContentEncoding("UTF-8");
  382. entity.setContentType("application/json");
  383. httpPost.setEntity(entity);
  384. httpPost.addHeader("Content-Type", "application/json;charset=UTF-8");
  385. httpPost.addHeader("Accept", "application/json");
  386. CloseableHttpClient httpClient = WxHttpUtil.getClosableHttpClient();
  387. CloseableHttpResponse response = httpClient.execute(httpPost);
  388. int statusCode = response.getStatusLine().getStatusCode();
  389. log.info("关闭微信订单:{},结果:{}", order.getTradeNo(), statusCode);
  390. if (statusCode == 204) {
  391. httpClient.close();
  392. dao.updatePayStatusOnly(order.getTradeNo(), TradeState.CLOSED.getCode());
  393. return ResultVoUtil.success("关闭订单成功。");
  394. }
  395. if (null != response.getEntity()) {
  396. String ret = EntityUtils.toString(response.getEntity());
  397. return ResultVoUtil.fail(ExceptionEnum.INTERNAL_SERVER_ERROR, ret);
  398. }
  399. return ResultVoUtil.fail(ExceptionEnum.NULL_POINTER, "关闭订单失败。");
  400. }
  401. public ResultVo<DoctorInfo> getDoctorInfo(String doctorCode) {
  402. DoctorInfo doctorInfo = dao.selectDoctorInfo(doctorCode);
  403. if (null == doctorInfo) {
  404. return ResultVoUtil.fail(ExceptionEnum.NULL_POINTER, "未找到医生信息!");
  405. }
  406. return ResultVoUtil.success(doctorInfo);
  407. }
  408. }