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 0000000..492272f Binary files /dev/null and b/xinelu-admin/src/main/resources/lib/hmac-1.0.jar differ diff --git a/xinelu-familydoctor/src/main/java/com/xinelu/familydoctor/applet/pojo/body/DzsCodeBody.java b/xinelu-familydoctor/src/main/java/com/xinelu/familydoctor/applet/pojo/body/DzsCodeBody.java new file mode 100644 index 0000000..47cf84e --- /dev/null +++ b/xinelu-familydoctor/src/main/java/com/xinelu/familydoctor/applet/pojo/body/DzsCodeBody.java @@ -0,0 +1,66 @@ +package com.xinelu.familydoctor.applet.pojo.body; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + * @Author mengkuiliang + * @Description 德州市居民码请求对象 + * @Date 2023-06-09 9:45 + * @Param + * @return + **/ +@Data +@ApiModel("德州市居民码请求对象") +public class DzsCodeBody { + + @ApiModelProperty(value = "二维码信息") + private String qrCode; + + @ApiModelProperty(value = "姓名", required = true) + private String name; + + @ApiModelProperty(value = "证件类型", required = true) + private String cardType; + + @ApiModelProperty(value = "证件号码", required = true) + private String cardNo; + + @ApiModelProperty(value = "电话号码", required = true) + private String phone; + + @ApiModelProperty(value = "民族 (01:汉族 02:蒙古族 03:回族 04:藏族 05:维吾尔族 06:苗族 07:彝族 08:壮族 09:布依族 10:朝鲜族 11:满族 12:侗族 13:瑶族 14:白族 15:土家族 16:哈尼族 17:哈萨克族 18:傣族 19:黎族 20:傈僳族 21:佤族 22:畲族 23:高山族 24:拉祜族 25:水族 26:东乡族 27:纳西族 28:景颇族 29:柯尔柯孜族 30:土族 31:达尔族 32:仫佬族 33:羌族 34:布朗族 35:撒拉族 36:毛南族 37:仡佬族 38:锡伯族 39:阿昌族 40:普米族 41:塔吉克族 42:怒族 43:乌孜别克族 44:俄罗斯族 45:鄂温克族 46:德昂族 47:保安族 48:裕固族 49:京族 50:塔塔尔族 51:独龙族 52:鄂伦春族 53:赫哲族 54:门巴族 55:珞巴族 56:基诺族 97:其他未识别的民族)", required = true) + private String nation; + + @ApiModelProperty(value = "性别 (1:男 2:女 9:未说明性别 )", required = true) + private String sex; + + @ApiModelProperty(value = "注册途径(11:APP 12:微信公众帐号 13:支付宝服务号 21:自助机 31:窗口 41:批量处理)", required = true) + private String regChannel; + + @ApiModelProperty(value = "注册模式(1线上、2线下)", required = true) + private String regMode; + + @ApiModelProperty(value = "个人审核材料上传地址(身份证时可空;非身份证时必填)") + private String path; + + @ApiModelProperty(value = "绑卡类型(对接时添加,后台字典的 证件类型--可绑定证)") + private String bindCardType; + + @ApiModelProperty(value = "绑卡卡号") + private String bindCardNo; + + @ApiModelProperty(value = "出生年月(yyyyMM 证件类型是身份证时可空,非身份证时必填)") + private String birthday; + + @ApiModelProperty(value = "地区编码") + private String addressAreaCode; + + @ApiModelProperty(value = "用户uid(调用省居民码接口返回)", required = true) + private String uid; + + @ApiModelProperty(value = "用户id(调用省居民码接口返回)", required = true) + private String qcUserId; + +} diff --git a/xinelu-familydoctor/src/main/java/com/xinelu/familydoctor/applet/pojo/query/DzsCodeQuery.java b/xinelu-familydoctor/src/main/java/com/xinelu/familydoctor/applet/pojo/query/DzsCodeQuery.java new file mode 100644 index 0000000..57f35e9 --- /dev/null +++ b/xinelu-familydoctor/src/main/java/com/xinelu/familydoctor/applet/pojo/query/DzsCodeQuery.java @@ -0,0 +1,36 @@ +package com.xinelu.familydoctor.applet.pojo.query; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + * @Author mengkuiliang + * @Description 德州市居民码查询对象 + * @Date 2023-06-09 9:45 + * @Param + * @return + **/ +@Data +@ApiModel("德州市居民码查询对象") +public class DzsCodeQuery { + + @ApiModelProperty(value = "接口名称(判断是否已注册居民码:isReg; 居民码用户注册:register; 根据UID获取居民码:getCodeByUid; 解析居民码二维码:parsingCode; 根据证件获取居民码:getCodeByCard; 获取用户信息:getUserInfo;)", required = true) + String interfaceName; + + @ApiModelProperty("省居民码授权码") + String jmmCode; + + @ApiModelProperty("用户标识") + String uid; + + @ApiModelProperty("证件类型(01:居民身份证; 03: 外国护照; 06: 港澳居民来往内地通行证; 07: 台湾居民来往内地通行证;)") + String cardType; + + @ApiModelProperty("证件号") + String cardNo; + + @ApiModelProperty("二维码") + String qrCode; + +}