From 8b548ac3ba5a79effa9c598fcc107fe46c8d74d1 Mon Sep 17 00:00:00 2001
From: mengkuiliang <1464081137@qq.com>
Date: Thu, 26 Oct 2023 09:11:30 +0800
Subject: [PATCH] =?UTF-8?q?=E8=BF=81=E7=A7=BB=E5=BE=B7=E5=B7=9E=E5=B8=82?=
=?UTF-8?q?=E9=B2=81=E9=80=9A=E7=A0=81=E6=8E=A5=E5=8F=A3=EF=BC=9B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
xinelu-admin/pom.xml | 8 +
.../web/controller/fd/DzsCodeController.java | 352 ++++++++++++++++++
.../web/controller/fd/utils/DzsCodeSign.java | 98 +++++
.../src/main/resources/application.yml | 23 +-
.../src/main/resources/lib/hmac-1.0.jar | Bin 0 -> 11422 bytes
.../applet/pojo/body/DzsCodeBody.java | 66 ++++
.../applet/pojo/query/DzsCodeQuery.java | 36 ++
7 files changed, 581 insertions(+), 2 deletions(-)
create mode 100644 xinelu-admin/src/main/java/com/xinelu/web/controller/fd/DzsCodeController.java
create mode 100644 xinelu-admin/src/main/java/com/xinelu/web/controller/fd/utils/DzsCodeSign.java
create mode 100644 xinelu-admin/src/main/resources/lib/hmac-1.0.jar
create mode 100644 xinelu-familydoctor/src/main/java/com/xinelu/familydoctor/applet/pojo/body/DzsCodeBody.java
create mode 100644 xinelu-familydoctor/src/main/java/com/xinelu/familydoctor/applet/pojo/query/DzsCodeQuery.java
diff --git a/xinelu-admin/pom.xml b/xinelu-admin/pom.xml
index e5f351f..c2752b0 100644
--- a/xinelu-admin/pom.xml
+++ b/xinelu-admin/pom.xml
@@ -86,6 +86,14 @@
xinelu-familydoctor
+
+
+ com.tzwy
+ hmac
+ 1.0
+ system
+ ${basedir}/src/main/resources/lib/hmac-1.0.jar
+
diff --git a/xinelu-admin/src/main/java/com/xinelu/web/controller/fd/DzsCodeController.java b/xinelu-admin/src/main/java/com/xinelu/web/controller/fd/DzsCodeController.java
new file mode 100644
index 0000000..c131fe3
--- /dev/null
+++ b/xinelu-admin/src/main/java/com/xinelu/web/controller/fd/DzsCodeController.java
@@ -0,0 +1,352 @@
+package com.xinelu.web.controller.fd;
+
+import com.alibaba.fastjson2.JSONObject;
+import com.xinelu.common.core.controller.BaseController;
+import com.xinelu.common.core.domain.R;
+import com.xinelu.common.exception.ServiceException;
+import com.xinelu.common.utils.DateUtils;
+import com.xinelu.common.utils.http.HttpService;
+import com.xinelu.common.utils.http.SslUtils;
+import com.xinelu.familydoctor.applet.pojo.body.DzsCodeBody;
+import com.xinelu.familydoctor.applet.pojo.query.DzsCodeQuery;
+import com.xinelu.web.controller.fd.utils.DzsCodeSign;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+@Api(tags = "德州市居民码控制器(爱山东APP)")
+@Slf4j
+@RestController
+@RequestMapping("/applet/fd/dzs/code")
+public class DzsCodeController extends BaseController {
+
+ @Resource
+ private HttpService httpService;
+ @Value("${dzsCode.appid}")
+ private String appid;
+ @Value("${dzsCode.appSecret}")
+ private String appSecret;
+ @Value("${dzsCode.url}")
+ private String url;
+ @Value("${dzsCode.mutualUrl}")
+ private String mutualUrl;
+ @Value("${dzsCode.isSecret}")
+ private String isSecret;
+ @Value("${dzsCode.isWhole}")
+ private String isWhole;
+ @Value("${dzsCode.institutionCode}")
+ private String institutionCode;
+ @Value("${dzsCode.useCityCode}")
+ private String useCityCode;
+ @Value("${dzsCode.businessStepCode}")
+ private String businessStepCode;
+
+ @ApiOperation(value = "居民码注册接口")
+ @GetMapping("/register")
+ public R> register(DzsCodeBody body) {
+ try {
+ JSONObject bodyParams = registerBody(body);
+ String message = "";
+ // 0:明文 1:加密
+ if (isSecret.equals("1")) {
+ message = DzsCodeSign.makeSign(appSecret, bodyParams.toJSONString());
+ } else {
+ message = bodyParams.toJSONString();
+ }
+ Map headerMap = headerParams(message);
+ // 0:明文 1:加密
+ JSONObject requestBody = new JSONObject();
+ requestBody.put("message", message);
+ SslUtils.ignoreSsl();
+ JSONObject resultObj = httpService.post(url + "/register", headerMap, requestBody);
+ log.info("居民码注册接口响应:{}", resultObj);
+ if (!resultObj.containsKey("code") || !resultObj.getString("code").equals("200")) {
+ if (resultObj.containsKey("msg") && !StringUtils.isBlank(resultObj.getString("msg"))) {
+ return R.fail(resultObj.getString("msg"));
+ }
+ return R.fail("接口异常");
+ }
+ if (resultObj.containsKey("data")) {
+ return R.ok(resultObj.get("data"));
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ throw new ServiceException(e.getMessage());
+ }
+ return R.ok();
+ }
+
+ @ApiOperation(value = "居民码查询接口")
+ @GetMapping("/analysis")
+ public R> query(DzsCodeQuery queryBody) {
+ try {
+ JSONObject bodyParams;
+ switch (queryBody.getInterfaceName()) {
+ // 是否已注册
+ case "isReg":
+ bodyParams = isRegBody(queryBody.getCardType(), queryBody.getCardNo());
+ break;
+ // 根据UID获取码值
+ case "getCodeByUid":
+ bodyParams = getCodeByUidBody(queryBody.getUid());
+ break;
+ // 根据证件获取居民码
+ case "getCodeByCard":
+ bodyParams = getCodeByCardBody(queryBody.getCardType(), queryBody.getCardNo());
+ break;
+ // 解析二维码
+ case "parsingCode":
+ bodyParams = parsingCodeBody(queryBody.getQrCode());
+ break;
+ // 获取用户信息
+ case "getUserInfo":
+ bodyParams = getUserInfoBody(queryBody.getJmmCode());
+ break;
+ default:
+ return R.fail("参数无效");
+ }
+ String message = "";
+ // 0:明文 1:加密
+ if (isSecret.equals("1")) {
+ message = DzsCodeSign.makeSign(appSecret, bodyParams.toJSONString());
+ } else {
+ message = bodyParams.toJSONString();
+ }
+ Map headerMap = headerParams(message);
+ // 0:明文 1:加密
+ JSONObject requestBody = new JSONObject();
+ requestBody.put("message", message);
+ SslUtils.ignoreSsl();
+ JSONObject resultObj = httpService.post(url + "/" + queryBody.getInterfaceName(), headerMap, requestBody);
+ log.info("居民码{}接口响应:{}", queryBody.getInterfaceName(), resultObj);
+ if (!resultObj.containsKey("code") || !resultObj.getString("code").equals("200")) {
+ if (resultObj.containsKey("msg") && !StringUtils.isBlank(resultObj.getString("msg"))) {
+ return R.fail(resultObj.getString("msg"));
+ }
+ return R.fail("接口异常");
+ }
+ if (resultObj.containsKey("data")) {
+ return R.ok(resultObj.getJSONObject("data"));
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ throw new ServiceException(e.getMessage());
+ }
+ return R.ok();
+ }
+
+ @ApiOperation(value = "多码互认接口")
+ @PostMapping("/mutualDiscern")
+ public R> mutualDiscern(@RequestBody DzsCodeBody body) {
+ // 请求参数示例:
+ // {
+ // "qrCode":"SDQR0000001A00010001371400011667058654613299203000169828216216982822221410ff8de0b234bdf8a92932e36173578789c4099bf3606c4c1e3e175a91a5ac754434bd767e4155ffdf095dc8a096714d4b86e535231efbb5383e9fd4cd13f2"
+ // }
+ if(body == null || StringUtils.isBlank(body.getQrCode())) {
+ return R.fail("请求参数无效");
+ }
+ try {
+ JSONObject bodyParams = mutualDiscernBody(body.getQrCode());
+ Map headerMap;
+ SslUtils.ignoreSsl();
+ String message = "";
+ JSONObject resultObj;
+ // 0:明文 1:加密
+ if (isSecret.equals("1")) {
+ message = DzsCodeSign.makeSign(appSecret, bodyParams.toJSONString());
+ headerMap = headerParams(message);
+ resultObj = httpService.post(mutualUrl + "/mutualDiscern", headerMap, message);
+ } else {
+ headerMap = headerParams(bodyParams.toJSONString());
+ resultObj = httpService.post(mutualUrl + "/mutualDiscern", headerMap, bodyParams);
+ }
+ log.info("居民码多码互认接口响应:{}", resultObj);
+ if (!resultObj.containsKey("code") || !resultObj.getString("code").equals("200")) {
+ if (resultObj.containsKey("msg") && !StringUtils.isBlank(resultObj.getString("msg"))) {
+ if (resultObj.containsKey("detail") && !StringUtils.isBlank(resultObj.getString("detail"))) {
+ return R.fail(resultObj.getString("msg") + "->" + resultObj.getString("detail"));
+ } else {
+ return R.fail(resultObj.getString("msg"));
+ }
+ }
+ return R.fail("接口异常");
+ }
+ if (resultObj.containsKey("data")) {
+ return R.ok(resultObj.getJSONObject("data"));
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ throw new ServiceException(e.getMessage());
+ }
+ return R.ok();
+ }
+
+ /**
+ * 设置请求header参数 参数都是必填的
+ */
+ private Map headerParams(String message) {
+ Map headMap = new HashMap<>();
+ // 客户端生成的安全随机码
+ String nonceStr = DzsCodeSign.getRandomString(10);
+ // 当前时间(yyyyMMddHHmmss)
+ String time = DateUtils.formatDate(new Date(), "yyyyMMddHHmmss");
+ // 根据appid、nonceStr、time和(message),经过HMAC算法计算的值
+ String str = appid + "&" + time + "&" + nonceStr;
+ if (isWhole.equals("1")) {
+ str += message;
+ }
+ System.out.println("签名字符串:" + str);
+
+ // 应用ID
+ headMap.put("appid", appid);
+ headMap.put("nonceStr", nonceStr);
+ headMap.put("time", time);
+ headMap.put("sign", DzsCodeSign.makeSign(appSecret, str));
+ headMap.put("isSecret", isSecret);
+ headMap.put("isWhole", isWhole);
+ return headMap;
+ }
+
+ /**
+ * 设置body参数 - 判断是否已注册居民码
+ */
+ private JSONObject isRegBody(String cardType, String cardNo) {
+ JSONObject jsonObject = new JSONObject();
+ // 证件类型 必
+ jsonObject.put("cardType", cardType);
+ // 证件编号 必
+ jsonObject.put("cardNo", cardNo);
+ return jsonObject;
+ }
+
+ /**
+ * 设置body参数 - 用户注册
+ */
+ private JSONObject registerBody(DzsCodeBody body) {
+ JSONObject jsonObject = new JSONObject();
+ return jsonObject;
+ }
+
+ /**
+ * 设置body参数 - 根据UID获取居民码
+ */
+ private JSONObject getCodeByUidBody(String uid) {
+ JSONObject jsonObject = new JSONObject();
+ // 居民码UID 必
+ jsonObject.put("uid", uid);
+ // 经度
+ jsonObject.put("longitude", "");
+ // 纬度
+ jsonObject.put("latitude", "");
+ // 县区编码
+ jsonObject.put("county", "");
+ // 街道
+ jsonObject.put("subdistrict", "");
+ // 地点
+ jsonObject.put("position", "");
+ return jsonObject;
+ }
+
+ /**
+ * 设置body参数 - 根据证件获取居民码
+ */
+ private JSONObject getCodeByCardBody(String cardType, String cardNo) {
+ JSONObject jsonObject = new JSONObject();
+ // 证件类型 必
+ jsonObject.put("cardType", cardType);
+ // 证件编号 必
+ jsonObject.put("cardNo", cardNo);
+ // 经度
+ jsonObject.put("longitude", "");
+ // 纬度
+ jsonObject.put("latitude", "");
+ // 县区编码
+ jsonObject.put("county", "");
+ // 街道
+ jsonObject.put("subdistrict", "");
+ // 地点
+ jsonObject.put("position", "");
+ return jsonObject;
+ }
+
+ /**
+ * 设置body参数 - 解析二维码
+ */
+ private JSONObject parsingCodeBody(String qrCode) {
+ JSONObject jsonObject = new JSONObject();
+ // 必
+ jsonObject.put("QrCode", qrCode);
+ // 机构编码 必
+ jsonObject.put("institutionCode", institutionCode);
+ // 终端类型(0人工、1自助)必
+ jsonObject.put("channelCode", "0");
+ // 业务环节 必
+ jsonObject.put("businessStepCode", businessStepCode);
+ // 用码时间 yyyy-MM-dd HH:mm:ss 必
+ jsonObject.put("useTime", DateUtils.formatDate(new Date(), "yyyy-MM-dd HH:mm:ss"));
+ // 返回值类型(对接时添加,后台的 二维码解析返回值类型)必
+ jsonObject.put("returnValueType", "01");
+ // 经度
+ jsonObject.put("longitude", "");
+ // 纬度
+ jsonObject.put("latitude", "");
+ // 县区编码
+ jsonObject.put("county", "");
+ // 街道
+ jsonObject.put("subdistrict", "");
+ // 地点
+ jsonObject.put("position", "");
+ return jsonObject;
+ }
+
+ /**
+ * 设置body参数 - 获取用户信息
+ */
+ private JSONObject getUserInfoBody(String jmmCode) {
+ JSONObject jsonObject = new JSONObject();
+ // 省居民码授权码 必
+ jsonObject.put("jmmCode", jmmCode);
+ return jsonObject;
+ }
+
+ /**
+ * 设置body参数 - 多码互认 二维码识读
+ */
+ private JSONObject mutualDiscernBody(String qrCode) {
+ JSONObject jsonObject = new JSONObject();
+ // 必
+ jsonObject.put("QrCode", qrCode);
+ // 城市编码 必
+ jsonObject.put("useCityCode", useCityCode);
+ // 机构编码 必
+ jsonObject.put("institutionCode", institutionCode);
+ // 终端类型(0人工、1自助)必
+ jsonObject.put("channelCode", "0");
+ // 业务环节(接口方提供) 必
+ jsonObject.put("businessStepCode", businessStepCode);
+ // 用卡时间 yyyy-MM-dd HH:mm:ss 必
+ jsonObject.put("useTime", DateUtils.formatDate(new Date(), "yyyy-MM-dd HH:mm:ss"));
+ // 返回值类型(对接时添加,后台的 二维码解析返回值类型)必
+ jsonObject.put("returnValueType", "01");
+ // 经度
+ jsonObject.put("longitude", "");
+ // 纬度
+ jsonObject.put("latitude", "");
+ // 县区编码 (市辖区:371401 德城区:371402 陵城区:371403 宁津县:371422 庆云县:371423 临邑县:371424 齐河县:371425 平原县:371426 夏津县:371427 武城县:371428 乐陵市:371481 禹城市:371482 天衢新区:371471)
+ jsonObject.put("county", "");
+ // 街道
+ jsonObject.put("subdistrict", "");
+ // 地点
+ jsonObject.put("position", "");
+ return jsonObject;
+ }
+
+}
diff --git a/xinelu-admin/src/main/java/com/xinelu/web/controller/fd/utils/DzsCodeSign.java b/xinelu-admin/src/main/java/com/xinelu/web/controller/fd/utils/DzsCodeSign.java
new file mode 100644
index 0000000..2b07e79
--- /dev/null
+++ b/xinelu-admin/src/main/java/com/xinelu/web/controller/fd/utils/DzsCodeSign.java
@@ -0,0 +1,98 @@
+package com.xinelu.web.controller.fd.utils;
+
+import cn.hutool.core.codec.Base64;
+import cn.hutool.core.util.HexUtil;
+import com.tzwy.hmac.sm3.utils.Sm3HmacUtils;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+import java.io.UnsupportedEncodingException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Random;
+
+/**
+ * @Author mengkuiliang
+ * @Description 德州市居民码签名工具类
+ * @Date 2023-06-08 16:45
+ * @Param
+ * @return
+ **/
+@Slf4j
+@Component
+public class DzsCodeSign {
+
+ /**
+ * @return java.lang.String
+ * @Author mengkuiliang
+ * @Description 生成签名
+ * @Date 2023-06-08 16:40
+ * @Param [appId, appSecret, isWhole]
+ **/
+ public static String createSign(String appId, String appSecret, String isWhole, String message) {
+ SimpleDateFormat formater = new SimpleDateFormat("yyyyMMddHHmmss");
+ String str;
+ if(isWhole.equals("0")) {
+ str = appId + "&" + formater.format(new Date()) + "&" + getRandomString(10);
+ } else {
+ // message前边没有 &
+ str = appId + "&" + formater.format(new Date()) + "&" + getRandomString(10) + message;
+ }
+ System.out.println("签名字符串:" + str);
+ return makeSign(appSecret, str);
+ }
+
+ /**
+ * @return boolean
+ * @Author mengkuiliang
+ * @Description 验证签名
+ * @Date 2023-06-08 16:43
+ * @Param [str, appSecret, sign]
+ **/
+ public static boolean verifySign(String str, String appSecret, String sign) {
+ String signS = makeSign(appSecret, str);
+ return signS.equals(sign);
+ }
+
+ /**
+ * 生成指定长度的随机字符串
+ */
+ public static String getRandomString(int length) {
+ String str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
+ Random random = new Random();
+ StringBuffer sb = new StringBuffer();
+ for (int i = 0; i < length; i++) {
+ int number = random.nextInt(62);
+ sb.append(str.charAt(number));
+ }
+ return sb.toString();
+ }
+
+ /**
+ * 生成签名
+ */
+ public static String makeSign(String appSecret, String str) {
+ try {
+ byte[] sm3Sign = Sm3HmacUtils.calcMac(HexUtil.decodeHex(appSecret), str.getBytes("UTF-8"));
+ return Base64.encode(sm3Sign);
+ } catch (Exception e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ public static void main(String[] args) throws UnsupportedEncodingException {
+
+ String appSecret = "74cb380c19b5448da4d7a1ef505089c4";
+ String appid = "lhw8570wl9zojyou6zv6";
+// String time = DateUtils.formatDate(new Date(), "yyyyMMddHHmmss");
+ String time = "20230609165045";
+// String nonceStr = DzsCodeSign.getRandomString(10);
+ String nonceStr = "8EqtkddxC2";
+ String str = appid + "&" + time + "&" + nonceStr;
+ String signS = makeSign(appSecret, str);
+ System.out.println(signS);
+
+ // JAlMiqpak3hwtTa+OzlWPcTuE2sSwn+F/I0j5/Cu3Mg=
+ }
+}
diff --git a/xinelu-admin/src/main/resources/application.yml b/xinelu-admin/src/main/resources/application.yml
index 05c60c8..479d194 100644
--- a/xinelu-admin/src/main/resources/application.yml
+++ b/xinelu-admin/src/main/resources/application.yml
@@ -249,8 +249,8 @@ xss:
# 家医配置
fd:
- dy: http://192.168.124.6:8001/fd/mp
- dz: http://192.168.124.6:8001/fd/mp
+ dz: http://8.131.93.145:54089/fd/mp
+ dy: http://8.131.93.145:54089/fd/mp
# 签约附近的机构多少公里内 <=0时不限制
distance: 0
@@ -316,3 +316,22 @@ logistics-config:
e-business-id: 1781371
api-key: 998b273d-c926-4659-a9d5-ae0613782d70
express-bird-url: https://api.kdniao.com/Ebusiness/EbusinessOrderHandle.aspx
+
+# 德州市鲁通码接口配置
+dzsCode:
+ appid: lhw8570wl9zojyou6zv6
+ appSecret: 74cb380c19b5448da4d7a1ef505089c4
+ # 接口服务
+ url: https://dsjj.dezhou.gov.cn/dzjmm/code
+ # 多码互认
+ mutualUrl: https://dsjj.dezhou.gov.cn/dzjmm/mutual
+ # 传输数据是否要加密(0:否1:是)
+ isSecret: 0
+ # 是否校验完整性(0:否1:是) 当为1时:sign的生成入参包括message
+ isWhole: 0
+ # 机构编码
+ institutionCode: 1137140000440159XP
+ # 城市编码 德州市
+ useCityCode: 371400
+ # 业务场景
+ businessStepCode: 302
diff --git a/xinelu-admin/src/main/resources/lib/hmac-1.0.jar b/xinelu-admin/src/main/resources/lib/hmac-1.0.jar
new file mode 100644
index 0000000000000000000000000000000000000000..492272f7ef8dd931e4e24a63fdb8451ff6158e0f
GIT binary patch
literal 11422
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