10 Commits 3699f800f1 ... 8c2cedab82

Author SHA1 Message Date
  lileilei 8c2cedab82 improve:65周岁以上预约放行不限制支持扫健康码或者刷证件 2 years ago
  lileilei 636b21be00 style:文案显示 2 years ago
  lileilei a5b932e94c style:日志,设备显示问题 2 years ago
  lileilei ac4757d6f0 add:多健康码平台白名单添加 2 years ago
  lileilei abd68beffd add gitignore 2 years ago
  lileilei 9b21f0e851 fix:景区预约接口调用签名;本地MOCK模拟测试数据待删除 2 years ago
  lileilei 9aaafca97a fix:空指针判断问题 2 years ago
  lileilei d46d4496cf add:景区预约对接 2 years ago
  lileilei 2b7ed36a97 add:开关门控制,页面显示健康码服务封装 2 years ago
  lileilei 295e3c47ac add:省共享工信行程 2 years ago
44 changed files with 1365 additions and 118 deletions
  1. 3 0
      .gitignore
  2. 2 1
      health-code.iml
  3. 1 1
      pom.xml
  4. 2 2
      src/main/java/com/yixin/ms/boot/component/aop/ServiceLog.java
  5. 53 0
      src/main/java/com/yixin/ms/boot/config/properties/ScenicProperties.java
  6. 9 0
      src/main/java/com/yixin/ms/controller/HealthCodeController.java
  7. 57 0
      src/main/java/com/yixin/ms/controller/IrsTripController.java
  8. 8 0
      src/main/java/com/yixin/ms/crypto/util/SzhzSm4Util.java
  9. 23 0
      src/main/java/com/yixin/ms/model/dto/HealthCodeDTO.java
  10. 19 0
      src/main/java/com/yixin/ms/model/dto/HealthMockDTO.java
  11. 29 0
      src/main/java/com/yixin/ms/model/dto/IrsTripCheckScheduleDTO.java
  12. 23 0
      src/main/java/com/yixin/ms/model/dto/IrsTripSendMsgDTO.java
  13. 2 0
      src/main/java/com/yixin/ms/model/dto/ShortTermDTO.java
  14. 21 0
      src/main/java/com/yixin/ms/model/enums/ColorEnum.java
  15. 1 1
      src/main/java/com/yixin/ms/model/enums/HealthCodeLevelEnum.java
  16. 19 8
      src/main/java/com/yixin/ms/model/enums/HealthVaccineNumEnum.java
  17. 43 0
      src/main/java/com/yixin/ms/model/enums/IrsTripAuthEnum.java
  18. 21 0
      src/main/java/com/yixin/ms/model/enums/NucleicRuleEnum.java
  19. 21 0
      src/main/java/com/yixin/ms/model/enums/NucleicStatusEnum.java
  20. 37 0
      src/main/java/com/yixin/ms/model/enums/TravelValidationEnum.java
  21. 21 2
      src/main/java/com/yixin/ms/model/vo/HealtCodeNucleinVO.java
  22. 7 3
      src/main/java/com/yixin/ms/model/vo/HealtCodeTypeVO.java
  23. 31 1
      src/main/java/com/yixin/ms/model/vo/HealtCodeUserVO.java
  24. 59 0
      src/main/java/com/yixin/ms/server/HealthCodeServer.java
  25. 51 0
      src/main/java/com/yixin/ms/server/HealthServer.java
  26. 94 24
      src/main/java/com/yixin/ms/server/IrsTripServer.java
  27. 83 0
      src/main/java/com/yixin/ms/server/ScenicServer.java
  28. 23 0
      src/main/java/com/yixin/ms/server/dto/HealthCodeDTO.java
  29. 1 1
      src/main/java/com/yixin/ms/server/dto/IrsSendMsgDTO.java
  30. 31 0
      src/main/java/com/yixin/ms/server/dto/ScenicConsumeDTO.java
  31. 31 0
      src/main/java/com/yixin/ms/server/dto/ScenicVerifyDTO.java
  32. 32 0
      src/main/java/com/yixin/ms/server/vo/HealthCodeVO.java
  33. 39 0
      src/main/java/com/yixin/ms/server/vo/IrsTripResultVO.java
  34. 21 0
      src/main/java/com/yixin/ms/server/vo/IrsTripTokenVO.java
  35. 3 0
      src/main/java/com/yixin/ms/server/vo/PlaceInfoVO.java
  36. 16 0
      src/main/java/com/yixin/ms/server/vo/ScenicConsumeVO.java
  37. 21 0
      src/main/java/com/yixin/ms/server/vo/ScenicVerifyVO.java
  38. 9 0
      src/main/java/com/yixin/ms/service/HealthCodeService.java
  39. 26 0
      src/main/java/com/yixin/ms/service/IrsTripService.java
  40. 253 73
      src/main/java/com/yixin/ms/service/impl/HealthCodeServiceImpl.java
  41. 49 0
      src/main/java/com/yixin/ms/service/impl/IrsTripServiceImpl.java
  42. 20 1
      src/main/resources/application-dev.yml
  43. 19 0
      src/main/resources/application-prod.yml
  44. 31 0
      src/main/resources/application-test.yml

+ 3 - 0
.gitignore

@@ -0,0 +1,3 @@
+/target/
+/logs/
+/.idea/

+ 2 - 1
health-code.iml

@@ -25,6 +25,7 @@
     </content>
     <orderEntry type="inheritedJdk" />
     <orderEntry type="sourceFolder" forTests="false" />
+    <orderEntry type="library" name="Maven: cn.hutool:hutool-all:5.7.0" level="project" />
     <orderEntry type="library" scope="TEST" name="Maven: org.springframework.boot:spring-boot-starter-test:2.4.5" level="project" />
     <orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter:2.4.5" level="project" />
     <orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-logging:2.4.5" level="project" />
@@ -112,7 +113,7 @@
     <orderEntry type="library" name="Maven: com.google.j2objc:j2objc-annotations:1.3" level="project" />
     <orderEntry type="library" name="Maven: com.squareup.okhttp3:okhttp:3.10.0" level="project" />
     <orderEntry type="library" name="Maven: com.squareup.okio:okio:1.14.0" level="project" />
-    <orderEntry type="library" name="Maven: cn.hutool:hutool-all:5.3.9" level="project" />
+    <orderEntry type="library" name="Maven: cn.hutool:hutool-all:5.7.0" level="project" />
     <orderEntry type="library" name="Maven: com.alibaba:fastjson:1.2.60" level="project" />
     <orderEntry type="library" name="Maven: com.github.binarywang:weixin-java-miniapp:4.0.7.B" level="project" />
     <orderEntry type="library" name="Maven: com.github.binarywang:weixin-java-common:4.0.7.B" level="project" />

+ 1 - 1
pom.xml

@@ -154,7 +154,7 @@
         <dependency>
             <groupId>cn.hutool</groupId>
             <artifactId>hutool-all</artifactId>
-            <version>5.3.9</version>
+            <version>5.7.0</version>
         </dependency>
 
         <dependency>

+ 2 - 2
src/main/java/com/yixin/ms/boot/component/aop/ServiceLog.java

@@ -13,8 +13,8 @@ import org.springframework.stereotype.Component;
  * 使用 aop 切面记录请求日志信息
  * </p>
  */
-@Aspect
-@Component
+//@Aspect
+//@Component
 @Slf4j
 public class ServiceLog {
     /**

+ 53 - 0
src/main/java/com/yixin/ms/boot/config/properties/ScenicProperties.java

@@ -0,0 +1,53 @@
+package com.yixin.ms.boot.config.properties;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+import java.util.List;
+
+/**
+ * @description: WxMaProperties <br>
+ * @date: 2021/4/13 10:18 <br>
+ * @author: PWB <br>
+ */
+@Configuration
+@Data
+@ConfigurationProperties(prefix = "scenic")
+public class ScenicProperties {
+    /**
+     * 根据预约码或者身份证查询是否预约
+     */
+    private String verifyUrl;
+
+    /**
+     * 根据预约码查询姓名、证件信息
+     */
+    private String queryUrl;
+
+    /**
+     * 游客放行推送
+     */
+    private String consumeUrl;
+
+    private List<Config> configs;
+
+    @Data
+    public static class Config {
+        /**
+         * 账号id
+         */
+        private Integer adminId;
+
+        /**
+         * 三方账号id
+         */
+        private String account;
+
+        /**
+         * 三方key
+         */
+        private String key;
+    }
+
+}

+ 9 - 0
src/main/java/com/yixin/ms/controller/HealthCodeController.java

@@ -5,8 +5,10 @@ import com.yixin.ms.boot.restful.RestResponse;
 import com.yixin.ms.boot.restful.RestResult;
 import com.yixin.ms.boot.restful.ServiceException;
 import com.yixin.ms.boot.uitls.SMSOrIdCardUtils;
+import com.yixin.ms.model.dto.HealthCodeDTO;
 import com.yixin.ms.model.dto.ShortTermDTO;
 import com.yixin.ms.model.vo.HealtCodeUserVO;
+import com.yixin.ms.server.vo.HealthCodeVO;
 import com.yixin.ms.service.HealthCodeService;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
@@ -63,4 +65,11 @@ public class HealthCodeController {
         }
         throw new ServiceException("删除失败");
     }
+
+    @ApiOperation("根据barcode查询健康码信息")
+    @PostMapping("/queryInfoByBarCode")
+    public RestResult<HealthCodeVO> queryInfoByBarCode(@RequestBody HealthCodeDTO healthCodeDTO){
+        return healthCodeService.queryInfoByBarCode(healthCodeDTO);
+    }
+
 }

+ 57 - 0
src/main/java/com/yixin/ms/controller/IrsTripController.java

@@ -0,0 +1,57 @@
+package com.yixin.ms.controller;
+
+
+import com.alibaba.fastjson.JSON;
+import com.yixin.ms.model.dto.IrsTripCheckScheduleDTO;
+import com.yixin.ms.model.dto.IrsTripSendMsgDTO;
+import com.yixin.ms.server.vo.IrsTripResultVO;
+import com.yixin.ms.service.IrsTripService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+
+/**
+ * @author lileilei
+ */
+@Slf4j
+@RequiredArgsConstructor(onConstructor = @__(@Autowired))
+@Api(tags = "省共享行程 API")
+@RestController
+@RequestMapping("/irs/trip")
+public class IrsTripController {
+    /**
+     * 服务对象
+     */
+    private final IrsTripService irsTripService;
+
+    @ApiOperation("行程核验查询短信验证服务")
+    @PostMapping("/sendMsg")
+    public String sendMsg(@RequestBody IrsTripSendMsgDTO sendMsgDTO) {
+        String body = null;
+        try {
+            body = irsTripService.sendMsg(sendMsgDTO);
+        } catch (Exception e) {
+            return JSON.toJSONString(new IrsTripResultVO<>().setCode(IrsTripResultVO.UNKNOWN_FLAG).setMsg("请求服务过于频繁,请重试"));
+        }
+        return body;
+    }
+
+    @ApiOperation("行程核验信息")
+    @PostMapping("/checkSchedule")
+    public String checkSchedule(@RequestBody IrsTripCheckScheduleDTO checkScheduleDTO) {
+        String body = null;
+        try {
+            body = irsTripService.checkSchedule(checkScheduleDTO);
+        } catch (Exception e) {
+            return JSON.toJSONString(new IrsTripResultVO<>().setCode(IrsTripResultVO.UNKNOWN_FLAG).setMsg("请求服务过于频繁,请重试"));
+        }
+        return body;
+    }
+}

+ 8 - 0
src/main/java/com/yixin/ms/crypto/util/SzhzSm4Util.java

@@ -20,4 +20,12 @@ public class SzhzSm4Util {
         final SymmetricCrypto symmetricCrypto = SmUtil.sm4(secret.getBytes());
         return symmetricCrypto.decryptStr(str);
     }
+
+    public static void main(String[] args) {
+        String encrypt = SzhzSm4Util.encrypt("36022dcb14f2f7d5", "cd16b30955d117b258bdd9afa5b3e958");
+        System.out.println(encrypt);
+
+        String decrypt = SzhzSm4Util.decrypt("36022dcb14f2f7d5", "BbeNjgmHFH1fDVKC5HEDCQ==");
+        System.out.println(decrypt);
+    }
 }

+ 23 - 0
src/main/java/com/yixin/ms/model/dto/HealthCodeDTO.java

@@ -0,0 +1,23 @@
+package com.yixin.ms.model.dto;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+/**
+ * @author: lileilei
+ * @date: 2022/8/5 13:55
+ * @description:
+ */
+@Data
+@Accessors(chain = true)
+public class HealthCodeDTO {
+    @ApiModelProperty("凭证标识")
+    private String appKey;
+
+    @ApiModelProperty("凭证密钥")
+    private String appSecret;
+
+    @ApiModelProperty("健康码标识")
+    private String barCode;
+}

+ 19 - 0
src/main/java/com/yixin/ms/model/dto/HealthMockDTO.java

@@ -0,0 +1,19 @@
+package com.yixin.ms.model.dto;
+
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+/**
+ * @author: lileilei
+ * @date: 2022/8/18 11:23
+ * @description:
+ */
+@Data
+@Accessors(chain = true)
+public class HealthMockDTO {
+    private Integer index;
+
+    private String mzt;
+
+    private Integer nucleicExpirationTime;
+}

+ 29 - 0
src/main/java/com/yixin/ms/model/dto/IrsTripCheckScheduleDTO.java

@@ -0,0 +1,29 @@
+package com.yixin.ms.model.dto;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+/**
+ * @author: lileilei
+ * @date: 2022/8/5 13:55
+ * @description:
+ */
+@Data
+@Accessors(chain = true)
+public class IrsTripCheckScheduleDTO {
+    @ApiModelProperty("凭证标识")
+    private String appKey;
+
+    @ApiModelProperty("凭证密钥")
+    private String appSecret;
+
+    @ApiModelProperty("手机号")
+    private String phone;
+
+    @ApiModelProperty("验证码")
+    private String verification;
+
+    @ApiModelProperty("中高风险地区编码组合,中高风险地区编码组合示例:420100,420200,420300(详见文件 ) 要求:必须精确到市级别,如果没有中高风险地区,city 传“0”即可")
+    private String city;
+}

+ 23 - 0
src/main/java/com/yixin/ms/model/dto/IrsTripSendMsgDTO.java

@@ -0,0 +1,23 @@
+package com.yixin.ms.model.dto;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+/**
+ * @author: lileilei
+ * @date: 2022/8/5 13:54
+ * @description:
+ */
+@Data
+@Accessors(chain = true)
+public class IrsTripSendMsgDTO {
+    @ApiModelProperty("凭证标识")
+    private String appKey;
+
+    @ApiModelProperty("凭证密钥")
+    private String appSecret;
+
+    @ApiModelProperty("手机号")
+    private String phone;
+}

+ 2 - 0
src/main/java/com/yixin/ms/model/dto/ShortTermDTO.java

@@ -3,10 +3,12 @@ package com.yixin.ms.model.dto;
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
+import lombok.experimental.Accessors;
 
 
 @ApiModel("健康码查询")
 @Data
+@Accessors(chain = true)
 public class ShortTermDTO {
 
     @ApiModelProperty(value = "token")

+ 21 - 0
src/main/java/com/yixin/ms/model/enums/ColorEnum.java

@@ -0,0 +1,21 @@
+package com.yixin.ms.model.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * @author: lileilei
+ * @date: 2022/8/9 16:22
+ * @description:
+ */
+@AllArgsConstructor
+@Getter
+public enum ColorEnum {
+    RED("#f60c0f", "红色"),
+    YELLOW("#ffc303", "黄色"),
+    GREEN("#09a661", "绿色"),
+    WHITE("#ffffff", "白色");
+
+    private String value;
+    private String desc;
+}

+ 1 - 1
src/main/java/com/yixin/ms/model/enums/HealthCodeLevelEnum.java

@@ -14,7 +14,7 @@ public enum HealthCodeLevelEnum {
     RED("red", "红码"),
     YELLOW("yellow", "黄码"),
     GREEN("green", "绿码"),
-    purple("purple", "橙码");
+    PURPLE("purple", "橙码");
 
     /**
      * 健康码状态: red-红色、yellow-黄色、green-绿色、purple-橙码

+ 19 - 8
src/main/java/com/yixin/ms/model/enums/HealthVaccineNumEnum.java

@@ -13,19 +13,18 @@ import lombok.Getter;
 public enum HealthVaccineNumEnum {
     /**
      * 0 未接种 对应0针
-     *
+     * <p>
      * 1-接种完成 对应2针
-     *
+     * <p>
      * 2-未完全接种 对应1针
-     *
+     * <p>
      * 3- 完成加强针 对应3针
      */
 
-    UN_VACCINATED("0", "未接种", "0"),
-    VACCINATION_COMPLETE("1", "接种完成", "2"),
-    INCOMPLETE_INOCULATION("2", "未完全接种", "1"),
-    Finishing_reinforcing_needle("3", "完成加强针", "3")
-    ;
+    UN_VACCINATED("0", "未接种", "0", "https://noise.hz-hanghui.com:8088/yx-fyzd/file/icon/nucleic/exclamation_mark.png"),
+    VACCINATION_COMPLETE("1", "接种完成", "2", "https://noise.hz-hanghui.com:8088/yx-fyzd/file/icon/nucleic/green_tick.png"),
+    INCOMPLETE_INOCULATION("2", "未完全接种", "1", "https://noise.hz-hanghui.com:8088/yx-fyzd/file/icon/nucleic/green_tick.png"),
+    Finishing_reinforcing_needle("3", "完成加强针", "3", "https://noise.hz-hanghui.com:8088/yx-fyzd/file/icon/nucleic/green_tick.png");
 
     /**
      * 疫苗状态标识
@@ -41,4 +40,16 @@ public enum HealthVaccineNumEnum {
      * 疫苗接种数量
      */
     private String num;
+
+    private String url;
+
+    public static HealthVaccineNumEnum numOf(String num) {
+        for (HealthVaccineNumEnum numEnum : values()) {
+            if(numEnum.getNum().equals(num)){
+                return numEnum;
+            }
+        }
+
+        return UN_VACCINATED;
+    }
 }

+ 43 - 0
src/main/java/com/yixin/ms/model/enums/IrsTripAuthEnum.java

@@ -0,0 +1,43 @@
+package com.yixin.ms.model.enums;
+
+import cn.hutool.core.util.IdUtil;
+import cn.hutool.core.util.StrUtil;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * @author: lileilei
+ * @date: 2022/8/5 17:17
+ * @description:
+ */
+@AllArgsConstructor
+@Getter
+public enum IrsTripAuthEnum {
+    /**
+     * 省共享行程权限认证信息
+     */
+    //公司内部使用
+    INTERNAL_USE("6f32f95d52c04eb6851b8b0e1a3155ad", "1b8f230d5904480ca3b6cc737c1211a2"),
+    TEST("cd7710c6d56e42838d12b83efe535020", "05ac7aa50dbc4083b7af9ecc42b9e5c4")
+    ;
+
+    private String appKey;
+
+    private String appSecret;
+
+    public static IrsTripAuthEnum appKeyOf(String appKey){
+        for(IrsTripAuthEnum authEnum : values()){
+            if(authEnum.getAppKey().equals(appKey)){
+                return authEnum;
+            }
+        }
+
+        return null;
+    }
+
+
+    public static void main(String[] args) {
+        System.out.println(IdUtil.fastSimpleUUID());
+        System.out.println(IdUtil.fastSimpleUUID());
+    }
+}

+ 21 - 0
src/main/java/com/yixin/ms/model/enums/NucleicRuleEnum.java

@@ -0,0 +1,21 @@
+package com.yixin.ms.model.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * @author: lileilei
+ * @date: 2022/8/9 17:57
+ * @description:
+ */
+@AllArgsConstructor
+@Getter
+public enum NucleicRuleEnum {
+    TWENTY_FOUR(24L, "24小时以内"),
+    FORTY_EIGHT(48L, "48小时以内"),
+    SEVENTY_TWO(72L, "72小时以内"),
+    SEVENTY_DAY(7L, "7天以内");
+
+    private Long value;
+    private String desc;
+}

+ 21 - 0
src/main/java/com/yixin/ms/model/enums/NucleicStatusEnum.java

@@ -0,0 +1,21 @@
+package com.yixin.ms.model.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * @author: lileilei
+ * @date: 2022/8/11 13:52
+ * @description:
+ */
+@AllArgsConstructor
+@Getter
+public enum NucleicStatusEnum {
+    NEGATIVE("阴性", "https://noise.hz-hanghui.com:8088/yx-fyzd/file/icon/nucleic/green_tick.png"),
+    POSITIVE("阳性", "https://noise.hz-hanghui.com:8088/yx-fyzd/file/icon/nucleic/red_tick.png"),
+    NOT_AVAILABLE("暂无", "https://noise.hz-hanghui.com:8088/yx-fyzd/file/icon/nucleic/exclamation_mark.png");
+
+    private String desc;
+
+    private String url;
+}

+ 37 - 0
src/main/java/com/yixin/ms/model/enums/TravelValidationEnum.java

@@ -0,0 +1,37 @@
+package com.yixin.ms.model.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * @author: lileilei
+ * @date: 2022/8/9 15:26
+ * @description:
+ */
+@Getter
+@AllArgsConstructor
+public enum TravelValidationEnum {
+    /**
+     * 1 未去过中高风险地区 2 去过中高风险地区 3 未查询到行程 4 未授权查询行程
+     */
+    NOT_BEEN_TO("1", "未去过中高风险地区", "https://noise.hz-hanghui.com:8088/yx-fyzd/file/icon/trip/green.png"),
+    HAVE_BEEN_TO("2", "去过中高风险地区", "https://noise.hz-hanghui.com:8088/yx-fyzd/file/icon/trip/red.png"),
+    NO_ITINERARY("3", "未查询到行程", "https://noise.hz-hanghui.com:8088/yx-fyzd/file/icon/trip/green.png"),
+    UNAUTHORIZED("4", "未授权查询行程", "https://noise.hz-hanghui.com:8088/yx-fyzd/file/icon/trip/green.png");
+
+    private String code;
+
+    private String desc;
+
+    private String url;
+
+    public static TravelValidationEnum codeOf(String code) {
+        for (TravelValidationEnum validationEnum : values()){
+            if(validationEnum.getCode().equals(code)){
+                return validationEnum;
+            }
+        }
+
+        return NO_ITINERARY;
+    }
+}

+ 21 - 2
src/main/java/com/yixin/ms/model/vo/HealtCodeNucleinVO.java

@@ -1,6 +1,7 @@
 package com.yixin.ms.model.vo;
 
 import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
 import lombok.experimental.Accessors;
 
@@ -15,21 +16,39 @@ public class HealtCodeNucleinVO {
 
 
     /**
-     * 报告时间 (接口前面已开放,参数说明歧义问题注意)
+     * 检测时间
      */
     @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
     private Date checktime;
 
     /**
-     * 检测时间
+     * 报告时间
      */
     @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
     private Date reportTime;
 
+    /**
+     * 核酸显示时间
+     */
+    @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date showTime;
+
     /**
      * 核酸结果
      */
     private String result;
 
+    @ApiModelProperty("图标地址")
+    private String iconUrl;
+
+    @ApiModelProperty("核酸标题")
+    private String title;
+
+    @ApiModelProperty("检测文案显示")
+    private String content;
+
+    @ApiModelProperty("检测文案字体颜色")
+    private String contentColor;
+
 }
 

+ 7 - 3
src/main/java/com/yixin/ms/model/vo/HealtCodeTypeVO.java

@@ -1,5 +1,6 @@
 package com.yixin.ms.model.vo;
 
+import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
 import lombok.experimental.Accessors;
 
@@ -7,13 +8,16 @@ import lombok.experimental.Accessors;
 @Data
 @Accessors(chain = true)
 public class HealtCodeTypeVO {
-
-
-
     /**
      * 健康码状态
      */
     private String mzt;
 
+    @ApiModelProperty("健康码提示内容")
+    private String content;
+
+    @ApiModelProperty("健康码提示内容字体颜色值")
+    private String contentColor;
+
 }
 

+ 31 - 1
src/main/java/com/yixin/ms/model/vo/HealtCodeUserVO.java

@@ -37,7 +37,37 @@ public class HealtCodeUserVO {
     @ApiModelProperty(value = "是否放行 0放行,1禁止通行")
     private Integer isOpen;
 
-    @ApiModelProperty(value = "禁止通行提示语")
+    @ApiModelProperty(value = "通行文案显示")
     private String openMsg;
+
+    @ApiModelProperty("标题说明")
+    private String title;
+
+    @ApiModelProperty("标题文字颜色")
+    private String titleColor;
+
+    @ApiModelProperty("背景颜色")
+    private String backgroundColor;
+
+    @ApiModelProperty("疫苗标题")
+    private String vaccineIcoTitle;
+
+    @ApiModelProperty("疫苗图标地址")
+    private String vaccineIconUrl;
+
+    @ApiModelProperty("疫苗接种数量字体颜色")
+    private String vaccineNumColor;
+
+    @ApiModelProperty("行程标题")
+    private String travelValidationTitle;
+
+    @ApiModelProperty("行程图标地址")
+    private String travelValidationIconUrl;
+
+    @ApiModelProperty("行程描述")
+    private String travelValidationDesc;
+
+    @ApiModelProperty("通行语音播报提示语")
+    private String openVoiceMsg;
 }
 

+ 59 - 0
src/main/java/com/yixin/ms/server/HealthCodeServer.java

@@ -0,0 +1,59 @@
+package com.yixin.ms.server;
+
+import cn.hutool.core.map.MapUtil;
+import cn.hutool.http.HttpRequest;
+import com.alibaba.fastjson.JSON;
+import com.yixin.ms.boot.restful.RestCode;
+import com.yixin.ms.boot.restful.RestResult;
+import com.yixin.ms.crypto.util.SzhzSm4Util;
+import com.yixin.ms.server.vo.HealthCodeVO;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+import java.util.Map;
+
+/**
+ * @author: lileilei
+ * @date: 2022/8/12 17:35
+ * @description: 杭州市数据局根据barcode查询健康码(国密接口)信息 仅政务网可访问
+ */
+@Slf4j
+@Component
+public class HealthCodeServer {
+    @Value("${irs.health.code.appKey}")
+    private String appKey;
+    @Value("${irs.health.code.appSecret}")
+    private String appSecret;
+    @Value("${irs.health.code.AppKey}")
+    private String AppKey;
+    @Value("${irs.health.code.queryInfoByBarCodeUrl}")
+    private String queryInfoByBarCodeUrl;
+
+    /**
+     * 通过barcode查询个人信息
+     * @param barcode
+     * @return
+     */
+    public RestResult<HealthCodeVO> queryInfoByBarCode(String barcode){
+        Map<String, String> headers = MapUtil.newHashMap();
+        headers.put("appKey", appKey);
+        headers.put("appSecret", appSecret);
+        Map<String, Object> paramMap = MapUtil.newHashMap();
+        paramMap.put("AppKey", AppKey);
+        paramMap.put("barcode", SzhzSm4Util.encrypt(AppKey, barcode));
+        String body = HttpRequest.post(queryInfoByBarCodeUrl).form(paramMap).addHeaders(headers).timeout(5000).execute().body();
+        log.info("通过barcode查询个人信息响应内容: {}", body);
+        RestResult restResult = JSON.parseObject(body, RestResult.class);
+        if(restResult.getCode() == RestCode.SUCCESS.getCode()){
+            HealthCodeVO healthCodeVO = JSON.parseObject(JSON.toJSONString(restResult.getData()), HealthCodeVO.class);
+            //反解密
+            healthCodeVO.setUserName(SzhzSm4Util.decrypt(AppKey, healthCodeVO.getUserName()));
+            healthCodeVO.setIdNum(SzhzSm4Util.decrypt(AppKey, healthCodeVO.getIdNum()));
+            healthCodeVO.setPn(SzhzSm4Util.decrypt(AppKey, healthCodeVO.getPn()));
+            restResult.setData(healthCodeVO);
+        }
+
+        return restResult;
+    }
+}

+ 51 - 0
src/main/java/com/yixin/ms/server/HealthServer.java

@@ -0,0 +1,51 @@
+package com.yixin.ms.server;
+
+import cn.hutool.http.HttpUtil;
+import com.alibaba.fastjson.JSON;
+import com.yixin.ms.boot.restful.RestCode;
+import com.yixin.ms.boot.restful.RestResult;
+import com.yixin.ms.boot.restful.ServiceException;
+import com.yixin.ms.crypto.util.SzhzSm4Util;
+import com.yixin.ms.server.dto.HealthCodeDTO;
+import com.yixin.ms.server.vo.HealthCodeVO;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+/**
+ * @author: lileilei
+ * @date: 2022/8/12 17:35
+ * @description: 杭州市数据局根据barcode查询健康码(国密接口)信息
+ */
+@Slf4j
+@Component
+public class HealthServer {
+    @Value("${health.appKey}")
+    private String appKey;
+    @Value("${health.appSecret}")
+    private String appSecret;
+    @Value("${health.queryInfoByBarCodeUrl}")
+    private String queryInfoByBarCodeUrl;
+
+    /**
+     * 通过barcode查询个人信息
+     * @param barcode
+     * @return
+     */
+    public RestResult<HealthCodeVO> queryInfoByBarCode(String barcode){
+        String body = null;
+        try {
+            body = HttpUtil.post(queryInfoByBarCodeUrl, JSON.toJSONString(new HealthCodeDTO().setAppKey(appKey).setAppSecret(appSecret).setBarCode(barcode)));
+        }catch (Exception e){
+            throw new ServiceException("barCode查询信息异常,请稍后重试");
+        }
+
+        RestResult restResult = JSON.parseObject(body, RestResult.class);
+        if(restResult.getCode() == RestCode.SUCCESS.getCode()){
+            HealthCodeVO healthCodeVO = JSON.parseObject(JSON.toJSONString(restResult.getData()), HealthCodeVO.class);
+            restResult.setData(healthCodeVO);
+        }
+
+        return restResult;
+    }
+}

+ 94 - 24
src/main/java/com/yixin/ms/server/IrsTripServer.java

@@ -1,12 +1,21 @@
 package com.yixin.ms.server;
 
+import cn.hutool.cache.CacheUtil;
+import cn.hutool.cache.impl.TimedCache;
 import cn.hutool.core.bean.BeanUtil;
 import cn.hutool.core.date.DateUtil;
 import cn.hutool.core.util.IdUtil;
 import cn.hutool.core.util.RandomUtil;
+import cn.hutool.core.util.StrUtil;
 import cn.hutool.crypto.SecureUtil;
+import cn.hutool.http.HttpUtil;
+import com.alibaba.fastjson.JSON;
 import com.yixin.ms.server.dto.IrsCheckScheduleDTO;
 import com.yixin.ms.server.dto.IrsSendMsgDTO;
+import com.yixin.ms.server.vo.IrsTripResultVO;
+import com.yixin.ms.server.vo.IrsTripTokenVO;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.codec.digest.Md5Crypt;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.http.HttpEntity;
@@ -23,8 +32,26 @@ import org.springframework.web.client.RestTemplate;
  * @date: 2022/8/3 17:37
  * @description: 省共享行程信息服务
  */
+@Slf4j
 @Component
 public class IrsTripServer {
+    private static final String TOKEN_HEADERS_KEY = "token_session_id";
+
+    private static final String REFRESH_TOKEN_HEADERS_KEY = "refresh_token_session_id";
+
+    /**
+     * 请求密钥有效时间为 15 分钟
+     *
+     * @see <a href="https://www.hutool.cn/docs/#/cache/TimedCache">定时缓存</a>
+     */
+    private static TimedCache<String, String> tokenCache = CacheUtil.newTimedCache(15 * 60 * 1000);
+
+    /**
+     * 刷新密钥有效时间为 48 小时
+     */
+    private static TimedCache<String, String> refreshTokenCache = CacheUtil.newTimedCache(48 * 60 * 60 * 1000);
+
+
     @Value("${irs.appKey}")
     private String appKey;
     @Value("${irs.appSecret}")
@@ -49,7 +76,20 @@ public class IrsTripServer {
      * 获取token
      * @return
      */
-    public String getToken(){
+    public IrsTripTokenVO getToken(){
+        IrsTripTokenVO tokenVO = null;
+        String tokenValue = tokenCache.get(TOKEN_HEADERS_KEY, false);
+        String refreshTokenValue = refreshTokenCache.get(REFRESH_TOKEN_HEADERS_KEY, false);
+        if(StrUtil.isNotEmpty(tokenValue) && StrUtil.isNotEmpty(refreshTokenValue)){
+            tokenVO = new IrsTripTokenVO();
+            tokenVO.setRequestSecret(tokenValue).setRefreshSecret(refreshTokenValue);
+            return tokenVO;
+        }
+
+        if(StrUtil.isEmpty(tokenValue) && StrUtil.isNotEmpty(refreshTokenValue)){
+            return this.refreshToken(refreshTokenValue);
+        }
+
         HttpHeaders headers = new HttpHeaders();
         headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
         MultiValueMap<String, String> map= new LinkedMultiValueMap();
@@ -60,7 +100,14 @@ public class IrsTripServer {
         map.add("sign", sign);
         HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(map, headers);
         ResponseEntity<String> response = restTemplate.postForEntity(tokenUrl, request , String.class);
-        return response.getBody();
+        IrsTripResultVO irsTripResultVO = JSON.parseObject(response.getBody(), IrsTripResultVO.class);
+        if(IrsTripResultVO.SUCCESS_FLAG.equals(irsTripResultVO.getCode())){
+            tokenVO =  JSON.parseObject(JSON.toJSONString(irsTripResultVO.getDatas()), IrsTripTokenVO.class);
+            tokenCache.put(TOKEN_HEADERS_KEY, tokenVO.getRequestSecret());
+            refreshTokenCache.put(REFRESH_TOKEN_HEADERS_KEY, tokenVO.getRefreshSecret());
+        }
+
+        return tokenVO;
     }
 
     /**
@@ -68,7 +115,8 @@ public class IrsTripServer {
      * @param refreshSecret
      * @return
      */
-    public String refreshToken(String refreshSecret){
+    public IrsTripTokenVO refreshToken(String refreshSecret){
+        IrsTripTokenVO tokenVO = null;
         HttpHeaders headers = new HttpHeaders();
         headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
         MultiValueMap<String, String> map= new LinkedMultiValueMap();
@@ -79,7 +127,15 @@ public class IrsTripServer {
         map.add("sign", sign);
         HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(map, headers);
         ResponseEntity<String> response = restTemplate.postForEntity(refreshTokenUrl, request , String.class);
-        return response.getBody();
+
+        IrsTripResultVO irsTripResultVO = JSON.parseObject(response.getBody(), IrsTripResultVO.class);
+        if(IrsTripResultVO.SUCCESS_FLAG.equals(irsTripResultVO.getCode())){
+            tokenVO =  JSON.parseObject(JSON.toJSONString(irsTripResultVO.getDatas()), IrsTripTokenVO.class);
+            tokenCache.put(TOKEN_HEADERS_KEY, tokenVO.getRequestSecret());
+            refreshTokenCache.put(REFRESH_TOKEN_HEADERS_KEY, tokenVO.getRefreshSecret());
+        }
+
+        return tokenVO;
     }
 
     /**
@@ -94,7 +150,9 @@ public class IrsTripServer {
         String sign = SecureUtil.md5(new StringBuilder().append(appKey).append(requestSecret).append(requestTime).toString()).toLowerCase();
         String secret = SecureUtil.md5(new StringBuilder().append(secretKey).append(dateTime).toString());
         irsSendMsgDTO.setRequestTime(requestTime).setSign(sign).setAppKey(appKey).setQueryId(IdUtil.fastSimpleUUID()).setSendTime(dateTime).setPhone(phone).setSecert(secret);
-        return restTemplate.getForObject(sendMsgUrl, String.class, BeanUtil.beanToMap(irsSendMsgDTO));
+        String body = HttpUtil.get(sendMsgUrl, BeanUtil.beanToMap(irsSendMsgDTO), 5000);
+        log.info("行程核验查询短信验证服务响应内容:{}", body);
+        return body;
     }
 
     /**
@@ -113,28 +171,40 @@ public class IrsTripServer {
         irsCheckScheduleDTO.setRequestTime(requestTime).setSign(sign).setAppKey(appKey).setQueryId(IdUtil.fastSimpleUUID()).setSendTime(dateTime).setPhone(phone).setSecert(secret);
         irsCheckScheduleDTO.setCity(city);
         irsCheckScheduleDTO.setVerification(verification);
-        return restTemplate.getForObject(checkScheduleUrl, String.class, BeanUtil.beanToMap(irsCheckScheduleDTO));
+        String body = HttpUtil.get(checkScheduleUrl, BeanUtil.beanToMap(irsCheckScheduleDTO), 5000);
+        log.info("行程核验信息响应内容:{}", body);
+        return body;
     }
 
     public static void main(String[] args) {
-        String appKey = "A330104242107202107000001", appSecret = "9a92d8b9d41f41729ab8ccbfaa547f12";
-        String requestTime = String.valueOf(System.currentTimeMillis());
-        System.out.println(requestTime);
-        String dateTime = DateUtil.formatDateTime(DateUtil.date(Long.valueOf(requestTime)));
-        System.out.println(dateTime);
-        String sign = SecureUtil.md5(appKey + appSecret + requestTime).toLowerCase();
-        System.out.println("获取token: " + sign);
-        sign = SecureUtil.md5(appKey + "bea7eb8906e14ea7a620f9ab7846db8f" + requestTime).toLowerCase();
-        System.out.println("刷新token: "+ sign);
-        //bbf754f8d3134d7292b7f363f20f533a
-        sign = SecureUtil.md5(appKey + "048755972cd24e1a9dca21dc6aac70cc" + requestTime).toLowerCase();
-        System.out.println(sign);
-        String secret = SecureUtil.md5("9@nr!#G8*Kuw" + dateTime);
-        System.out.println(secret);
-        System.out.println(RandomUtil.randomString(32));
-        System.out.println("#############################");
-        String pwd = SecureUtil.md5("admin@123" + "hh_f03920fbc2512e12" + "1659354702").toLowerCase();
-        System.out.println(pwd);
+//        String appKey = "A330104242107202107000001", appSecret = "9a92d8b9d41f41729ab8ccbfaa547f12";
+//        String requestTime = String.valueOf(System.currentTimeMillis());
+//        System.out.println(requestTime);
+//        String dateTime = DateUtil.formatDateTime(DateUtil.date(Long.valueOf(requestTime)));
+//        System.out.println(dateTime);
+//        String sign = SecureUtil.md5(appKey + appSecret + requestTime).toLowerCase();
+//        System.out.println("获取token: " + sign);
+//        sign = SecureUtil.md5(appKey + "bea7eb8906e14ea7a620f9ab7846db8f" + requestTime).toLowerCase();
+//        System.out.println("刷新token: "+ sign);
+//        //bbf754f8d3134d7292b7f363f20f533a
+//        sign = SecureUtil.md5(appKey + "048755972cd24e1a9dca21dc6aac70cc" + requestTime).toLowerCase();
+//        System.out.println(sign);
+//        String secret = SecureUtil.md5("9@nr!#G8*Kuw" + dateTime);
+//        System.out.println(secret);
+//        System.out.println(RandomUtil.randomString(32));
+//        System.out.println("#############################");
+//        String pwd = SecureUtil.md5("admin@123" + "hh_f03920fbc2512e12" + "1659354702").toLowerCase();
+//        System.out.println(pwd);
+        String str = "{\"account\":\"1510813918447009792\",\"idNumber\":\"456\",\"reservationCode\":\"123\",\"timestamp\":\"1660717723806\"}74d9aed80d7a4c7181ba9615fae0fc11";
+        String signStr = SecureUtil.md5(str);
+        System.out.println(signStr);
+
+        String rt = String.valueOf(System.currentTimeMillis());
+
+        String ak = "83d9174a3e614cf4bb95767551dd7ec6", sk = "fca0ee49199347f793e54175090193e6";
+        String sn = SecureUtil.md5(ak+sk+rt);
+        System.out.println(rt);
+        System.out.println(sn);
     }
 
 }

+ 83 - 0
src/main/java/com/yixin/ms/server/ScenicServer.java

@@ -0,0 +1,83 @@
+package com.yixin.ms.server;
+
+import cn.hutool.crypto.SecureUtil;
+import cn.hutool.http.HttpUtil;
+import com.alibaba.fastjson.JSON;
+import com.yixin.ms.boot.config.properties.ScenicProperties;
+import com.yixin.ms.boot.restful.ServiceException;
+import com.yixin.ms.server.dto.ScenicConsumeDTO;
+import com.yixin.ms.server.dto.ScenicVerifyDTO;
+import com.yixin.ms.server.vo.ScenicConsumeVO;
+import com.yixin.ms.server.vo.ScenicVerifyVO;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * @author: lileilei
+ * @date: 2022/8/3 17:37
+ * @description: 皋亭山景区预约服务
+ */
+@Slf4j
+@Component
+public class ScenicServer {
+    @Autowired
+    private ScenicProperties scenicProperties;
+
+    /**
+     * 根据预约码或者身份证查询是否预约
+     * @param dto
+     * @return
+     */
+    public ScenicVerifyVO verify(ScenicVerifyDTO dto, Integer adminId) {
+        List<ScenicProperties.Config> configs = scenicProperties.getConfigs();
+        Map<Integer, ScenicProperties.Config> configMap = configs.stream().collect(Collectors.toMap(ScenicProperties.Config::getAdminId, v -> v, (v1, v2) -> v1));
+        ScenicProperties.Config config = configMap.get(adminId);
+        if(config == null || !config.getAdminId().equals(adminId)){
+            throw new ServiceException("景区预约账号未配置,请检查");
+        }
+
+        dto.setTimestamp(String.valueOf(System.currentTimeMillis())).setAccount(config.getAccount());
+        //签名
+        dto.setSign(SecureUtil.md5(new StringBuilder().append(JSON.toJSONString(dto)).append(config.getKey()).toString()));
+        String body = null;
+        try {
+            body = HttpUtil.post(scenicProperties.getVerifyUrl(), JSON.toJSONString(dto));
+        }catch (Exception e){
+            throw new ServiceException("景区预约服务系统异常,请稍后重试");
+        }
+        log.info("根据预约码或者身份证查询是否预约,入参信息:{}, 响应信息:{}", JSON.toJSONString(dto),  body);
+        ScenicVerifyVO verifyVO = JSON.parseObject(body, ScenicVerifyVO.class);
+        return verifyVO;
+    }
+
+    /**
+     * 游客放行推送
+     * @param dto
+     * @return
+     */
+    public ScenicConsumeVO consume(ScenicConsumeDTO dto, Integer adminId){
+        List<ScenicProperties.Config> configs = scenicProperties.getConfigs();
+        Map<Integer, ScenicProperties.Config> configMap = configs.stream().collect(Collectors.toMap(ScenicProperties.Config::getAdminId, v -> v, (v1, v2) -> v1));
+        ScenicProperties.Config config = configMap.get(adminId);
+        if(!config.getAdminId().equals(adminId)){
+            throw new ServiceException("景区预约账号未配置,请检查");
+        }
+
+        dto.setTimestamp(String.valueOf(System.currentTimeMillis())).setAccount(config.getAccount());
+        //签名
+        dto.setSign(SecureUtil.md5(new StringBuilder().append(JSON.toJSONString(dto)).append(config.getKey()).toString()));
+        String body = null;
+        try {
+            body = HttpUtil.post(scenicProperties.getConsumeUrl(), JSON.toJSONString(dto));
+        }catch (Exception e){
+            throw new ServiceException("景区预约服务系统异常,请稍后重试");
+        }
+        log.info("游客放行入参信息:{}, 推送响应内容:{}", JSON.toJSONString(dto), body);
+        return JSON.parseObject(body, ScenicConsumeVO.class);
+    }
+}

+ 23 - 0
src/main/java/com/yixin/ms/server/dto/HealthCodeDTO.java

@@ -0,0 +1,23 @@
+package com.yixin.ms.server.dto;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+/**
+ * @author: lileilei
+ * @date: 2022/8/12 17:38
+ * @description:
+ */
+@Data
+@Accessors(chain = true)
+public class HealthCodeDTO {
+    @ApiModelProperty("标识")
+    private String appKey;
+
+    @ApiModelProperty("密钥")
+    private String appSecret;
+
+    @ApiModelProperty("健康码标识")
+    private String barCode;
+}

+ 1 - 1
src/main/java/com/yixin/ms/server/dto/IrsSendMsgDTO.java

@@ -23,5 +23,5 @@ public class IrsSendMsgDTO {
 
     private String secert;
 
-    private String Phone;
+    private String phone;
 }

+ 31 - 0
src/main/java/com/yixin/ms/server/dto/ScenicConsumeDTO.java

@@ -0,0 +1,31 @@
+package com.yixin.ms.server.dto;
+
+import com.fasterxml.jackson.annotation.JsonPropertyOrder;
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+/**
+ * @author: lileilei
+ * @date: 2022/8/17 10:31
+ * @description:
+ */
+@Data
+@Accessors(chain = true)
+@JsonPropertyOrder(value = "a,i,r,s,t")
+public class ScenicConsumeDTO {
+    /**
+     * 身份证
+     */
+    private String idNumber;
+
+    /**
+     * 预约码
+     */
+    private String reservationCode;
+
+    private String account;
+
+    private String timestamp;
+
+    private String sign;
+}

+ 31 - 0
src/main/java/com/yixin/ms/server/dto/ScenicVerifyDTO.java

@@ -0,0 +1,31 @@
+package com.yixin.ms.server.dto;
+
+import com.alibaba.fastjson.JSON;
+import com.fasterxml.jackson.annotation.JsonPropertyOrder;
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+/**
+ * @author: lileilei
+ * @date: 2022/8/17 10:24
+ * @description:
+ */
+@Data
+@Accessors(chain = true)
+@JsonPropertyOrder(value = "a,i,r,s,t")
+public class ScenicVerifyDTO {
+    /**
+     * 身份证
+     */
+    private String idNumber;
+    /**
+     * 预约码
+     */
+    private String reservationCode;
+
+    private String account;
+
+    private String timestamp;
+
+    private String sign;
+}

+ 32 - 0
src/main/java/com/yixin/ms/server/vo/HealthCodeVO.java

@@ -0,0 +1,32 @@
+package com.yixin.ms.server.vo;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+/**
+ * @author: lileilei
+ * @date: 2022/8/12 17:38
+ * @description:
+ */
+@Data
+@Accessors(chain = true)
+public class HealthCodeVO {
+    @ApiModelProperty("健康码状态")
+    private String colorCode;
+
+    @ApiModelProperty("姓名")
+    private String userName;
+
+    @ApiModelProperty("证件号")
+    private String idNum;
+
+    @ApiModelProperty("手机号")
+    private String pn;
+
+    @ApiModelProperty("证件类型")
+    private String idType;
+
+    @ApiModelProperty("健康码标识")
+    private String barCode;
+}

+ 39 - 0
src/main/java/com/yixin/ms/server/vo/IrsTripResultVO.java

@@ -0,0 +1,39 @@
+package com.yixin.ms.server.vo;
+
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+/**
+ * @author: lileilei
+ * @date: 2022/8/5 14:18
+ * @description:
+ */
+@Data
+@Accessors(chain = true)
+public class IrsTripResultVO<T> {
+    /**
+     * 成功状态码
+     */
+    public static final String SUCCESS_FLAG = "00";
+    /**
+     * 验证token失败状态码
+     */
+    public static final String FAIL_TOKEN_FLAG = "999";
+    /**
+     * 请求未知异常
+     */
+    public static final String UNKNOWN_FLAG = "1000";
+
+    private String code;
+    private String msg;
+
+    private T datas;
+
+    private String requestId;
+
+    private Integer dataCount;
+
+    private Integer totalDataCount;
+
+    private Integer totalPage;
+}

+ 21 - 0
src/main/java/com/yixin/ms/server/vo/IrsTripTokenVO.java

@@ -0,0 +1,21 @@
+package com.yixin.ms.server.vo;
+
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+/**
+ * @author: lileilei
+ * @date: 2022/8/5 14:21
+ * @description:
+ */
+@Data
+@Accessors(chain = true)
+public class IrsTripTokenVO {
+    private String refreshSecret;
+
+    private Long refreshSecretEndTime;
+
+    private String requestSecret;
+
+    private Long requestSecretEndTime;
+}

+ 3 - 0
src/main/java/com/yixin/ms/server/vo/PlaceInfoVO.java

@@ -31,4 +31,7 @@ public class PlaceInfoVO {
 
     @ApiModelProperty("核酸有效期时间,单位小时,0:不限")
     private Integer nucleicExpirationTime;
+
+    @ApiModelProperty("景区预约标识")
+    private Boolean scenicFlag;
 }

+ 16 - 0
src/main/java/com/yixin/ms/server/vo/ScenicConsumeVO.java

@@ -0,0 +1,16 @@
+package com.yixin.ms.server.vo;
+
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+/**
+ * @author: lileilei
+ * @date: 2022/8/17 10:30
+ * @description:
+ */
+@Data
+@Accessors(chain = true)
+public class ScenicConsumeVO {
+    private Boolean status;
+    private String message;
+}

+ 21 - 0
src/main/java/com/yixin/ms/server/vo/ScenicVerifyVO.java

@@ -0,0 +1,21 @@
+package com.yixin.ms.server.vo;
+
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+/**
+ * @author: lileilei
+ * @date: 2022/8/17 10:22
+ * @description:
+ */
+@Data
+@Accessors(chain = true)
+public class ScenicVerifyVO {
+    private Boolean releaseStatus;
+
+    private String userName;
+
+    private String idNumber;
+
+    private String message;
+}

+ 9 - 0
src/main/java/com/yixin/ms/service/HealthCodeService.java

@@ -2,8 +2,10 @@ package com.yixin.ms.service;
 
 
 import com.yixin.ms.boot.restful.RestResult;
+import com.yixin.ms.model.dto.HealthCodeDTO;
 import com.yixin.ms.model.dto.ShortTermDTO;
 import com.yixin.ms.model.vo.HealtCodeUserVO;
+import com.yixin.ms.server.vo.HealthCodeVO;
 
 public interface HealthCodeService {
 
@@ -24,5 +26,12 @@ public interface HealthCodeService {
      * @return
      */
     RestResult getShortTerm(ShortTermDTO shortTermDTO);
+
+    /**
+     * 根据barcode查询健康码信息
+     * @param healthCodeDTO
+     * @return
+     */
+    RestResult<HealthCodeVO> queryInfoByBarCode(HealthCodeDTO healthCodeDTO);
 }
 

+ 26 - 0
src/main/java/com/yixin/ms/service/IrsTripService.java

@@ -0,0 +1,26 @@
+package com.yixin.ms.service;
+
+import com.yixin.ms.model.dto.IrsTripCheckScheduleDTO;
+import com.yixin.ms.model.dto.IrsTripSendMsgDTO;
+
+/**
+ * @author: lileilei
+ * @date: 2022/8/5 13:48
+ * @description:
+ */
+public interface IrsTripService {
+
+    /**
+     * 行程核验查询短信验证服务
+     * @param sendMsgDTO
+     * @return
+     */
+    String sendMsg(IrsTripSendMsgDTO sendMsgDTO);
+
+    /**
+     * 行程核验信息
+     * @param checkScheduleDTO
+     * @return
+     */
+    String checkSchedule(IrsTripCheckScheduleDTO checkScheduleDTO);
+}

+ 253 - 73
src/main/java/com/yixin/ms/service/impl/HealthCodeServiceImpl.java

@@ -1,8 +1,10 @@
 package com.yixin.ms.service.impl;
 
+import cn.hutool.core.collection.CollectionUtil;
+import cn.hutool.core.date.DateUnit;
 import cn.hutool.core.date.DateUtil;
 import cn.hutool.core.lang.UUID;
-import cn.hutool.core.util.StrUtil;
+import cn.hutool.core.util.*;
 import cn.hutool.http.HttpUtil;
 import com.alibaba.excel.util.CollectionUtils;
 import com.alibaba.fastjson.JSON;
@@ -16,21 +18,19 @@ import com.yixin.ms.boot.uitls.DESUtil;
 import com.yixin.ms.boot.uitls.OkHttpUtils;
 import com.yixin.ms.boot.uitls.RedisUtil;
 import com.yixin.ms.boot.uitls.SMSOrIdCardUtils;
+import com.yixin.ms.model.dto.HealthCodeDTO;
+import com.yixin.ms.model.dto.HealthMockDTO;
 import com.yixin.ms.model.dto.ShortTermDTO;
-import com.yixin.ms.model.enums.HealthCodeLevelEnum;
-import com.yixin.ms.model.enums.HealthOpenEnum;
-import com.yixin.ms.model.enums.HealthVaccineNumEnum;
-import com.yixin.ms.model.enums.IDTypeEnum;
+import com.yixin.ms.model.enums.*;
 import com.yixin.ms.model.vo.HealtCodeNucleinVO;
 import com.yixin.ms.model.vo.HealtCodeTypeVO;
 import com.yixin.ms.model.vo.HealtCodeUserVO;
+import com.yixin.ms.server.HealthCodeServer;
+import com.yixin.ms.server.HealthServer;
 import com.yixin.ms.server.PlaceHealthCodeServer;
-import com.yixin.ms.server.dto.DeviceScanCodeDTO;
-import com.yixin.ms.server.dto.DeviceScanIdCardDTO;
-import com.yixin.ms.server.dto.PlaceRecordDTO;
-import com.yixin.ms.server.vo.DeviceScanIdCardVO;
-import com.yixin.ms.server.vo.HealthResultVO;
-import com.yixin.ms.server.vo.PlaceInfoVO;
+import com.yixin.ms.server.ScenicServer;
+import com.yixin.ms.server.dto.*;
+import com.yixin.ms.server.vo.*;
 import com.yixin.ms.service.HealthCodeService;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.StringUtils;
@@ -46,13 +46,48 @@ import java.util.concurrent.Executor;
 @Slf4j
 @Service
 public class HealthCodeServiceImpl implements HealthCodeService {
+    /**
+     * 65周岁预约不限制通行
+     */
+    private static final int AGE = 65;
+
     @Autowired
     private RedisUtil redisUtil;
     @Autowired
     private PlaceHealthCodeServer placeHealthCodeServer;
     @Autowired
+    private HealthCodeServer healthCodeServer;
+    @Autowired
+    private HealthServer healthServer;
+    @Autowired
+    private ScenicServer scenicServer;
+    @Autowired
     private Executor executor;
 
+    //顺序
+//    private static Integer index = 0;
+//    private static List<HealthMockDTO> mockDTOList = Lists.newArrayList();
+
+//    static {
+//        if (CollectionUtil.isEmpty(mockDTOList)) {
+//            mockDTOList.add(new HealthMockDTO().setIndex(0).setMzt("绿码").setNucleicExpirationTime(0));
+//            mockDTOList.add(new HealthMockDTO().setIndex(1).setMzt("绿码").setNucleicExpirationTime(24));
+//            mockDTOList.add(new HealthMockDTO().setIndex(2).setMzt("绿码").setNucleicExpirationTime(48));
+//            mockDTOList.add(new HealthMockDTO().setIndex(3).setMzt("绿码").setNucleicExpirationTime(72));
+//            mockDTOList.add(new HealthMockDTO().setIndex(4).setMzt("绿码").setNucleicExpirationTime(168));
+//            mockDTOList.add(new HealthMockDTO().setIndex(5).setMzt("黄码").setNucleicExpirationTime(0));
+//            mockDTOList.add(new HealthMockDTO().setIndex(6).setMzt("黄码").setNucleicExpirationTime(24));
+//            mockDTOList.add(new HealthMockDTO().setIndex(7).setMzt("黄码").setNucleicExpirationTime(48));
+//            mockDTOList.add(new HealthMockDTO().setIndex(8).setMzt("黄码").setNucleicExpirationTime(72));
+//            mockDTOList.add(new HealthMockDTO().setIndex(9).setMzt("黄码").setNucleicExpirationTime(168));
+//            mockDTOList.add(new HealthMockDTO().setIndex(10).setMzt("红码").setNucleicExpirationTime(0));
+//            mockDTOList.add(new HealthMockDTO().setIndex(11).setMzt("红码").setNucleicExpirationTime(24));
+//            mockDTOList.add(new HealthMockDTO().setIndex(12).setMzt("红码").setNucleicExpirationTime(48));
+//            mockDTOList.add(new HealthMockDTO().setIndex(13).setMzt("红码").setNucleicExpirationTime(72));
+//            mockDTOList.add(new HealthMockDTO().setIndex(14).setMzt("红码").setNucleicExpirationTime(168));
+//        }
+//    }
+
     @Override
     public HealtCodeUserVO getShortTermCertificate(ShortTermDTO shortTermDTO) {
         try {
@@ -93,6 +128,7 @@ public class HealthCodeServiceImpl implements HealthCodeService {
 
     @Override
     public RestResult getShortTerm(ShortTermDTO shortTermDTO) {
+        log.info("健康码查询入参信息:{}", JSON.toJSONString(shortTermDTO));
         if (StrUtil.isEmpty(shortTermDTO.getHealthCode())) {
             if (StringUtils.isBlank(shortTermDTO.getIdNumber()) && StringUtils.isBlank(shortTermDTO.getName())) {
                 throw new ServiceException(701, "身份证和姓名为空哦");
@@ -107,21 +143,21 @@ public class HealthCodeServiceImpl implements HealthCodeService {
          * \\000026https://h5.dingtalk.com/healthAct/index.html?stateCouncilBarCode=ffd631d12c5ee80fe77fe0fb4347b4e8&flag=1#/result
          * 扫码参数兼容问题处理
          */
-        if(StrUtil.isNotEmpty(shortTermDTO.getHealthCode()) && shortTermDTO.getHealthCode().contains("http")){
+        if (StrUtil.isNotEmpty(shortTermDTO.getHealthCode()) && shortTermDTO.getHealthCode().contains("http")) {
             shortTermDTO.setHealthCode(shortTermDTO.getHealthCode().substring(shortTermDTO.getHealthCode().indexOf("http")));
         }
 
-        if(StrUtil.isNotEmpty(shortTermDTO.getHealthCode()) && shortTermDTO.getHealthCode().contains("HTTP")){
+        if (StrUtil.isNotEmpty(shortTermDTO.getHealthCode()) && shortTermDTO.getHealthCode().contains("HTTP")) {
             shortTermDTO.setHealthCode(shortTermDTO.getHealthCode().substring(shortTermDTO.getHealthCode().indexOf("HTTP")));
         }
 
-        if(StrUtil.isNotEmpty(shortTermDTO.getHealthCode()) && (HttpUtil.isHttp(shortTermDTO.getHealthCode()) || HttpUtil.isHttps(shortTermDTO.getHealthCode()))){
+        if (StrUtil.isNotEmpty(shortTermDTO.getHealthCode()) && (HttpUtil.isHttp(shortTermDTO.getHealthCode()) || HttpUtil.isHttps(shortTermDTO.getHealthCode()))) {
             log.info("请求健康码参数信息: {}", shortTermDTO.getHealthCode());
             //扫码推送参数为链接,提取杭州健康码barCode参数信息
             String barCode = HttpUtil.decodeParamMap(shortTermDTO.getHealthCode().toLowerCase(), Charset.defaultCharset()).get("statecouncilbarcode");
-            if(StrUtil.isEmpty(barCode)){
+            if (StrUtil.isEmpty(barCode)) {
                 throw new ServiceException("请打开支付宝选择杭州健康码重试");
-            }else {
+            } else {
                 shortTermDTO.setHealthCode(barCode);
             }
         }
@@ -129,9 +165,10 @@ public class HealthCodeServiceImpl implements HealthCodeService {
         HealtCodeUserVO userVO = null;
         //核酸有效期时间,默认0不限制
         Integer nucleicExpirationTime = 0;
+        PlaceInfoVO placeInfoVO = null;
         if (StringUtils.isNotEmpty(shortTermDTO.getSn())) {
             RestResult<PlaceInfoVO> placeInfoResult = placeHealthCodeServer.queryPlaceInfoBySn(shortTermDTO.getSn());
-            if (placeInfoResult != null && placeInfoResult.getData() != null){
+            if (placeInfoResult != null && placeInfoResult.getData() != null) {
                 nucleicExpirationTime = placeInfoResult.getData().getNucleicExpirationTime();
             }
 
@@ -140,49 +177,54 @@ public class HealthCodeServiceImpl implements HealthCodeService {
             }
 
             if (placeInfoResult != null && placeInfoResult.getData() != null && StrUtil.isNotEmpty(placeInfoResult.getData().getAddressCode()) && RestCode.SUCCESS.getCode() == placeInfoResult.getCode()) {
-                PlaceInfoVO placeInfoVO = placeInfoResult.getData();
+                placeInfoVO = placeInfoResult.getData();
                 HealthResultVO<DeviceScanIdCardVO> scanIdCardResult = null;
                 if (StrUtil.isNotEmpty(shortTermDTO.getHealthCode())) {
-                    //健康码查询场所码信息
-                    scanIdCardResult = placeHealthCodeServer.deviceScanCode(new DeviceScanCodeDTO().setDeviceId(shortTermDTO.getSn()).setHealthCodeQrCode(shortTermDTO.getHealthCode()).setUrlParams(shortTermDTO.getHealthCode()));
-                    //换绑场所码或者管理员健康码首次会提示设备未打卡等错误,重新发起调用
-                    if (scanIdCardResult == null || !scanIdCardResult.getCode().equals(Integer.valueOf(RestCode.SUCCESS.getCode()))) {
-                        if (scanIdCardResult != null && scanIdCardResult.getCode().equals(Integer.valueOf(RestCode.INVALID_TOKEN.getCode()))) {
-                            //清理token缓存
-                            placeHealthCodeServer.getToken(true);
-                        }
+                    //非景区预约,通用健康码查询
+                    if (!placeInfoVO.getScenicFlag()) {
+                        scanIdCardResult = this.deviceScanCode(shortTermDTO);
+                    }
 
-                        try {
-                            Thread.sleep(500);
-                        } catch (InterruptedException e) {
-                            e.printStackTrace();
+                    //景区预约,走预约码查询
+                    if (placeInfoVO.getScenicFlag() && NumberUtil.isNumber(shortTermDTO.getHealthCode())) {
+                        ScenicVerifyVO verifyVO = scenicServer.verify(new ScenicVerifyDTO().setReservationCode(shortTermDTO.getHealthCode()), placeInfoVO.getAdminId());
+                        if (!verifyVO.getReleaseStatus()) {
+                            throw new ServiceException(verifyVO.getMessage());
+                        } else {
+                            shortTermDTO.setName(verifyVO.getUserName()).setIdNumber(verifyVO.getIdNumber());
+                            scanIdCardResult = this.deviceScanIdCardEncrypt(shortTermDTO);
                         }
-                        scanIdCardResult = placeHealthCodeServer.deviceScanCode(new DeviceScanCodeDTO().setDeviceId(shortTermDTO.getSn()).setHealthCodeQrCode(shortTermDTO.getHealthCode()).setUrlParams(shortTermDTO.getHealthCode()));
-                    }
-                } else {
-                    DeviceScanIdCardDTO scanIdCardDTO = new DeviceScanIdCardDTO().setDeviceId(shortTermDTO.getSn()).setIdNumber(shortTermDTO.getIdNumber());
-                    if(SMSOrIdCardUtils.isIdCard(shortTermDTO.getIdNumber())){
-                        scanIdCardDTO.setIdType(IDTypeEnum.ID_CARD.getCode());
-                    }else{
-                        scanIdCardDTO.setIdType(IDTypeEnum.PASSPORT.getCode());
                     }
 
-                    //扫人员证件查询场所码信息
-                    scanIdCardResult = placeHealthCodeServer.deviceScanIdCardEncrypt(scanIdCardDTO);
-                    //换绑场所码或者管理员健康码首次会提示设备未打卡等错误,重新发起调用
-                    if (scanIdCardResult == null || !scanIdCardResult.getCode().equals(Integer.valueOf(RestCode.SUCCESS.getCode()))) {
-                        if (scanIdCardResult != null && scanIdCardResult.getCode().equals(Integer.valueOf(RestCode.INVALID_TOKEN.getCode()))) {
-                            //清理token缓存
-                            placeHealthCodeServer.getToken(true);
+                    //景区预约,走健康码查询
+                    if (placeInfoVO.getScenicFlag() && !NumberUtil.isNumber(shortTermDTO.getHealthCode())) {
+                        RestResult<HealthCodeVO> restResult = healthServer.queryInfoByBarCode(shortTermDTO.getHealthCode());
+                        HealthCodeVO healthCodeVO = null;
+
+                        if (restResult.getCode() == RestCode.SUCCESS.getCode()) {
+                            healthCodeVO = restResult.getData();
                         }
 
-                        try {
-                            Thread.sleep(500);
-                        } catch (InterruptedException e) {
-                            e.printStackTrace();
+                        if (healthCodeVO != null && StrUtil.isNotEmpty(healthCodeVO.getIdNum()) && IdcardUtil.getAgeByIdCard(healthCodeVO.getIdNum()) < AGE) {
+                            //判断是否小于65周岁查询预约放行权限
+                            ScenicVerifyVO verifyVO = scenicServer.verify(new ScenicVerifyDTO().setIdNumber(healthCodeVO.getIdNum()), placeInfoVO.getAdminId());
+                            if (!verifyVO.getReleaseStatus()) {
+                                throw new ServiceException(verifyVO.getMessage());
+                            }
+                        }
+
+                        scanIdCardResult = this.deviceScanCode(shortTermDTO.setName(healthCodeVO.getUserName()).setIdNumber(healthCodeVO.getIdNum()));
+                    }
+                } else {
+                    if (placeInfoVO.getScenicFlag() && IdcardUtil.getAgeByIdCard(shortTermDTO.getIdNumber()) < AGE) {
+                        //判断是否小于65周岁查询预约放行权限
+                        ScenicVerifyVO verifyVO = scenicServer.verify(new ScenicVerifyDTO().setIdNumber(shortTermDTO.getIdNumber()), placeInfoVO.getAdminId());
+                        if (!verifyVO.getReleaseStatus()) {
+                            throw new ServiceException(verifyVO.getMessage());
                         }
-                        scanIdCardResult = placeHealthCodeServer.deviceScanIdCardEncrypt(scanIdCardDTO);
                     }
+
+                    scanIdCardResult = this.deviceScanIdCardEncrypt(shortTermDTO);
                 }
 
                 if (scanIdCardResult == null || !scanIdCardResult.getCode().equals(Integer.valueOf(RestCode.SUCCESS.getCode()))) {
@@ -193,10 +235,10 @@ public class HealthCodeServiceImpl implements HealthCodeService {
                     userVO = new HealtCodeUserVO();
                     //疫苗接种数量
                     userVO.setVaccineNum(scanIdCardVO.getVaccinated());
-                    if(StrUtil.isNotEmpty(scanIdCardVO.getCheckTime())){
+                    if (StrUtil.isNotEmpty(scanIdCardVO.getCheckTime())) {
                         //核酸记录
                         userVO.setNuclein(Lists.newArrayList(new HealtCodeNucleinVO().setChecktime(DateUtil.parseDateTime(scanIdCardVO.getCheckTime())).setReportTime(StrUtil.isEmpty(scanIdCardVO.getReportTime()) ? null : DateUtil.parseDateTime(scanIdCardVO.getReportTime())).setResult(scanIdCardVO.getCheckResult())));
-                    }else{
+                    } else {
                         userVO.setNuclein(Lists.newArrayList());
                     }
 
@@ -234,6 +276,10 @@ public class HealthCodeServiceImpl implements HealthCodeService {
             userVO.setUsername(StrUtil.isEmpty(userVO.getUsername()) ? shortTermDTO.getName() : userVO.getUsername());
             //身份证号
             userVO.setIdNumber(StrUtil.isEmpty(userVO.getIdNumber()) ? shortTermDTO.getIdNumber() : userVO.getIdNumber());
+
+            //姓名和身份证号脱敏处理
+            userVO.setUsername(StrUtil.isEmpty(userVO.getUsername()) ? StrUtil.EMPTY : DesensitizedUtil.chineseName(userVO.getUsername()));
+            userVO.setIdNumber(StrUtil.isEmpty(userVO.getIdNumber()) ? StrUtil.EMPTY : DesensitizedUtil.idCardNum(userVO.getIdNumber(), 4, 4));
             /**
              * 0 未接种 对应0针
              *
@@ -243,50 +289,182 @@ public class HealthCodeServiceImpl implements HealthCodeService {
              *
              * 3- 完成加强针 对应3针
              */
-            if(HealthVaccineNumEnum.VACCINATION_COMPLETE.getStatus().equals(userVO.getVaccineNum())){
+            if (HealthVaccineNumEnum.VACCINATION_COMPLETE.getStatus().equals(userVO.getVaccineNum())) {
                 userVO.setVaccineNum(HealthVaccineNumEnum.VACCINATION_COMPLETE.getNum());
-            }else if(HealthVaccineNumEnum.INCOMPLETE_INOCULATION.getStatus().equals(userVO.getVaccineNum())){
+            } else if (HealthVaccineNumEnum.INCOMPLETE_INOCULATION.getStatus().equals(userVO.getVaccineNum())) {
                 userVO.setVaccineNum(HealthVaccineNumEnum.INCOMPLETE_INOCULATION.getNum());
-            }else{
+            } else {
                 userVO.setVaccineNum(userVO.getVaccineNum());
             }
 
             //用户健康码信息
             HealtCodeTypeVO codeTypeVO = userVO.getData().stream().findFirst().get();
+            //TODO MOCK 测试 start
+//            HealthMockDTO mockDTO = mockDTOList.get(index);
+//            codeTypeVO.setMzt(mockDTO.getMzt());
+//            nucleicExpirationTime = mockDTO.getNucleicExpirationTime();
+//            index++;
+//            if (index > 14) {
+//                index = 0;
+//            }
+            //TODO MOCK 测试 end
+
             //用户核酸信息
             List<HealtCodeNucleinVO> nucleinList = userVO.getNuclein();
+            if (CollectionUtil.isNotEmpty(nucleinList)) {
+                nucleinList.forEach(v -> v.setShowTime(v.getReportTime() == null ? v.getChecktime() : DateUtil.compare(v.getChecktime(), v.getReportTime()) > 0 ? v.getChecktime() : v.getReportTime()));
+            }
+
+            if (HealthCodeLevelEnum.GREEN.getDesc().equals(codeTypeVO.getMzt())) {
+                codeTypeVO.setContent("绿码:健康码状态为低风险").setContentColor(ColorEnum.GREEN.getValue());
+                userVO.setBackgroundColor(ColorEnum.GREEN.getValue());
+            }
+
+            //核酸过期标识
+            boolean nucleicExpirationFlag = false;
             /**
              * 开门规则判定校验:红黄码,核酸有效期
              */
-            if(!HealthCodeLevelEnum.GREEN.getDesc().equals(codeTypeVO.getMzt())){
+            if (!HealthCodeLevelEnum.GREEN.getDesc().equals(codeTypeVO.getMzt())) {
                 userVO.setIsOpen(HealthOpenEnum.NO_THOROUGHFARE.getCode());
-                userVO.setOpenMsg(new StringBuilder().append(codeTypeVO.getMzt()).append("禁止通行").toString());
-            }else if(nucleicExpirationTime != null && !nucleicExpirationTime.equals(0) && (CollectionUtils.isEmpty(nucleinList) || DateUtil.betweenMs(nucleinList.stream().findFirst().get().getChecktime(), DateUtil.date()) > nucleicExpirationTime * 60 * 60 * 1000)){
+                String openMsg = new StringBuilder().append(codeTypeVO.getMzt()).append("禁止通行").toString();
+                userVO.setOpenMsg(openMsg).setOpenVoiceMsg(openMsg);
+                if (HealthCodeLevelEnum.RED.getDesc().equals(codeTypeVO.getMzt())) {
+                    codeTypeVO.setContent(new StringBuilder().append(codeTypeVO.getMzt()).append(":健康码状态为高风险").toString()).setContentColor(ColorEnum.RED.getValue());
+                    userVO.setBackgroundColor(ColorEnum.RED.getValue());
+                } else {
+                    codeTypeVO.setContent(new StringBuilder().append(codeTypeVO.getMzt()).append(": 健康码状态为中风险").toString()).setContentColor(ColorEnum.YELLOW.getValue());
+                    userVO.setBackgroundColor(ColorEnum.YELLOW.getValue());
+                }
+            } else if (nucleicExpirationTime != null && !nucleicExpirationTime.equals(0) && (CollectionUtils.isEmpty(nucleinList) || DateUtil.betweenMs(nucleinList.stream().findFirst().get().getShowTime(), DateUtil.date()) > nucleicExpirationTime * 60 * 60 * 1000)) {
+                nucleicExpirationFlag = true;
                 userVO.setIsOpen(HealthOpenEnum.NO_THOROUGHFARE.getCode());
-                userVO.setOpenMsg(new StringBuilder().append(nucleicExpirationTime).append("小时内无有效核酸阴性记录,禁止通行").toString());
-            }else{
-                userVO.setIsOpen(HealthOpenEnum.RELEASE.getCode());
-                userVO.setOpenMsg("绿码,请通行");
+                String openMsg;
+                if (nucleicExpirationTime <= NucleicRuleEnum.SEVENTY_TWO.getValue()) {
+                    openMsg = new StringBuilder().append(nucleicExpirationTime).append("小时内无有效核酸阴性记录,禁止通行").toString();
+                } else {
+                    openMsg = new StringBuilder().append((nucleicExpirationTime / 24) + 1).append("天内无有效核酸阴性记录,禁止通行").toString();
+                }
+                userVO.setOpenMsg(openMsg).setOpenVoiceMsg(openMsg);
+            } else {
+                if (placeInfoVO != null && placeInfoVO.getScenicFlag()) {
+                    userVO.setIsOpen(HealthOpenEnum.RELEASE.getCode()).setOpenMsg("绿码,欢迎入园").setOpenVoiceMsg("绿码,欢迎入园");
+                } else {
+                    userVO.setIsOpen(HealthOpenEnum.RELEASE.getCode()).setOpenMsg("绿码,请通行").setOpenVoiceMsg("绿码,请通行");
+                }
             }
 
-            //TODO 是否放行对接测试
-            /*
-            List<Integer> openList = Lists.newArrayList(0, 1);
-            userVO.setIsOpen(openList.get(RandomUtil.randomInt(0, 2)));
-
-            if(userVO.getIsOpen().equals(1)){
-                List<String> openMsgList = Lists.newArrayList("测试文案: 核酸已过有效期,禁止通行", "测试文案: 黄码,禁止通行", "测试文案: 红码,禁止通行");
-                userVO.setOpenMsg(openMsgList.get(RandomUtil.randomInt(0, 3)));
-            }else{
-                userVO.setOpenMsg("绿码,请通行");
-            }*/
+            if (CollectionUtil.isNotEmpty(nucleinList)) {
+                HealtCodeNucleinVO nucleinVO = nucleinList.stream().findFirst().get();
+                nucleinVO.setTitle("核酸检测").setContentColor(nucleicExpirationFlag ? ColorEnum.YELLOW.getValue() : ColorEnum.GREEN.getValue()).setIconUrl(nucleicExpirationFlag ? NucleicStatusEnum.NOT_AVAILABLE.getUrl() : NucleicStatusEnum.NEGATIVE.getUrl());
+                if (nucleinVO.getReportTime() != null && DateUtil.compare(nucleinVO.getChecktime(), nucleinVO.getReportTime()) > 0) {
+//                    nucleinVO.setContent(new StringBuilder().append(NucleicRuleEnum.TWENTY_FOUR.getValue()).append("H").append(" 结果未出").toString());
+                    nucleinVO.setContent(new StringBuilder().append("结果未出").toString());
+                } else {
+                    long betweenHour = DateUtil.between(nucleinVO.getShowTime(), DateUtil.date(), DateUnit.HOUR);
+                    if (betweenHour < NucleicRuleEnum.TWENTY_FOUR.getValue()) {
+                        nucleinVO.setContent(new StringBuilder().append(NucleicRuleEnum.TWENTY_FOUR.getValue()).append(" ").append(nucleinVO.getResult()).toString());
+                    } else if (betweenHour < NucleicRuleEnum.FORTY_EIGHT.getValue()) {
+                        nucleinVO.setContent(new StringBuilder().append(NucleicRuleEnum.FORTY_EIGHT.getValue()).append(" ").append(nucleinVO.getResult()).toString());
+                    } else if (betweenHour < NucleicRuleEnum.SEVENTY_TWO.getValue()) {
+                        nucleinVO.setContent(new StringBuilder().append(NucleicRuleEnum.SEVENTY_TWO.getValue()).append(" ").append(nucleinVO.getResult()).toString());
+                    } else {
+                        nucleinVO.setContent(new StringBuilder().append(DateUtil.betweenDay(nucleinVO.getShowTime(), DateUtil.date(), false) + 1).append("天 ").append(nucleinVO.getResult()).toString());
+                    }
+                }
+            } else {
+                userVO.setNuclein(Lists.newArrayList(new HealtCodeNucleinVO().setTitle("核酸检测").setIconUrl(NucleicStatusEnum.NOT_AVAILABLE.getUrl()).setContent("暂无").setContentColor(ColorEnum.GREEN.getValue())));
+            }
 
+            userVO.setTitle("健 康 码").setTitleColor(ColorEnum.WHITE.getValue())
+                    //疫苗信息
+                    .setVaccineIcoTitle("新冠疫苗").setVaccineIconUrl(HealthVaccineNumEnum.numOf(userVO.getVaccineNum()).getUrl()).setVaccineNumColor(ColorEnum.GREEN.getValue())
+                    //行程信息
+                    .setTravelValidationDesc(TravelValidationEnum.codeOf(userVO.getTravelValidation()).getDesc()).setTravelValidationTitle("动态行程卡").setTravelValidationIconUrl(TravelValidationEnum.codeOf(userVO.getTravelValidation()).getUrl());
+            final PlaceInfoVO finalPlaceInfoVO = placeInfoVO;
+            final HealtCodeUserVO finalUserVO = userVO;
+            executor.execute(() -> {
+                if (finalPlaceInfoVO != null && finalPlaceInfoVO.getScenicFlag() && finalUserVO.getIsOpen().equals(HealthOpenEnum.RELEASE.getCode())) {
+                    scenicServer.consume(new ScenicConsumeDTO().setIdNumber(shortTermDTO.getIdNumber()).setReservationCode(NumberUtil.isNumber(shortTermDTO.getHealthCode()) ? shortTermDTO.getHealthCode() : StrUtil.EMPTY), finalPlaceInfoVO.getAdminId());
+                }
+            });
             return RestResponse.ok(userVO);
         }
 
         throw new ServiceException(703, "未查询到此数据");
     }
 
+    /**
+     * 根据barCode查询健康码信息
+     *
+     * @param shortTermDTO
+     * @return
+     */
+    private HealthResultVO<DeviceScanIdCardVO> deviceScanCode(ShortTermDTO shortTermDTO) {
+        //健康码查询场所码信息
+        HealthResultVO<DeviceScanIdCardVO> scanIdCardResult = placeHealthCodeServer.deviceScanCode(new DeviceScanCodeDTO().setDeviceId(shortTermDTO.getSn()).setHealthCodeQrCode(shortTermDTO.getHealthCode()).setUrlParams(shortTermDTO.getHealthCode()));
+        //换绑场所码或者管理员健康码首次会提示设备未打卡等错误,重新发起调用
+        if (scanIdCardResult == null || !scanIdCardResult.getCode().equals(Integer.valueOf(RestCode.SUCCESS.getCode()))) {
+            if (scanIdCardResult != null && scanIdCardResult.getCode().equals(Integer.valueOf(RestCode.INVALID_TOKEN.getCode()))) {
+                //清理token缓存
+                placeHealthCodeServer.getToken(true);
+            }
+
+            try {
+                Thread.sleep(500);
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+            }
+            scanIdCardResult = placeHealthCodeServer.deviceScanCode(new DeviceScanCodeDTO().setDeviceId(shortTermDTO.getSn()).setHealthCodeQrCode(shortTermDTO.getHealthCode()).setUrlParams(shortTermDTO.getHealthCode()));
+        }
+
+        return scanIdCardResult;
+    }
+
+    /**
+     * 根据证件号查询健康码信息
+     *
+     * @param shortTermDTO
+     * @return
+     */
+    private HealthResultVO<DeviceScanIdCardVO> deviceScanIdCardEncrypt(ShortTermDTO shortTermDTO) {
+        DeviceScanIdCardDTO scanIdCardDTO = new DeviceScanIdCardDTO().setDeviceId(shortTermDTO.getSn()).setIdNumber(shortTermDTO.getIdNumber());
+        if (SMSOrIdCardUtils.isIdCard(shortTermDTO.getIdNumber())) {
+            scanIdCardDTO.setIdType(IDTypeEnum.ID_CARD.getCode());
+        } else {
+            scanIdCardDTO.setIdType(IDTypeEnum.PASSPORT.getCode());
+        }
+
+        //扫人员证件查询场所码信息
+        HealthResultVO<DeviceScanIdCardVO> scanIdCardResult = placeHealthCodeServer.deviceScanIdCardEncrypt(scanIdCardDTO);
+        //换绑场所码或者管理员健康码首次会提示设备未打卡等错误,重新发起调用
+        if (scanIdCardResult == null || !scanIdCardResult.getCode().equals(Integer.valueOf(RestCode.SUCCESS.getCode()))) {
+            if (scanIdCardResult != null && scanIdCardResult.getCode().equals(Integer.valueOf(RestCode.INVALID_TOKEN.getCode()))) {
+                //清理token缓存
+                placeHealthCodeServer.getToken(true);
+            }
+
+            try {
+                Thread.sleep(500);
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+            }
+            scanIdCardResult = placeHealthCodeServer.deviceScanIdCardEncrypt(scanIdCardDTO);
+        }
+
+        return scanIdCardResult;
+    }
+
+    @Override
+    public RestResult<HealthCodeVO> queryInfoByBarCode(HealthCodeDTO healthCodeDTO) {
+        IrsTripAuthEnum authEnum = IrsTripAuthEnum.appKeyOf(healthCodeDTO.getAppKey());
+        if (authEnum == null || !authEnum.getAppSecret().equals(healthCodeDTO.getAppSecret())) {
+            return RestResponse.error("调用权限校验失败,请检查");
+        }
+
+        return healthCodeServer.queryInfoByBarCode(healthCodeDTO.getBarCode());
+    }
+
     public HealtCodeUserVO getShortTermUser(String sfzh, String name) throws Exception {
         log.info("江干分局健康码传参,身份证{} 姓名 {}", sfzh, name);
         JSONObject jsonObject = new JSONObject();
@@ -306,6 +484,8 @@ public class HealthCodeServiceImpl implements HealthCodeService {
             if (StringUtils.equals("00", json.getString("error_code"))) {
                 healtCodeUserVO.setData(JSON.parseArray(json.getJSONArray("data").toJSONString(), HealtCodeTypeVO.class));
                 healtCodeUserVO.setNuclein(JSON.parseArray(json.getJSONArray("nuclein").toJSONString(), HealtCodeNucleinVO.class));
+                //源数据只返回报告时间,检测时间先等同于报告时间,方便后面逻辑处理
+//                healtCodeUserVO.getNuclein().forEach(v -> v.setReportTime(v.getChecktime()));
                 healtCodeUserVO.setVaccineNum(json.getString("vaccine_num"));
             } else {
                 throw new ServiceException("江干分局异常:" + jsonObject.getString("msg"));

+ 49 - 0
src/main/java/com/yixin/ms/service/impl/IrsTripServiceImpl.java

@@ -0,0 +1,49 @@
+package com.yixin.ms.service.impl;
+
+import com.alibaba.fastjson.JSON;
+import com.yixin.ms.model.dto.IrsTripCheckScheduleDTO;
+import com.yixin.ms.model.dto.IrsTripSendMsgDTO;
+import com.yixin.ms.model.enums.IrsTripAuthEnum;
+import com.yixin.ms.server.IrsTripServer;
+import com.yixin.ms.server.vo.IrsTripResultVO;
+import com.yixin.ms.server.vo.IrsTripTokenVO;
+import com.yixin.ms.service.IrsTripService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+/**
+ * @author: lileilei
+ * @date: 2022/8/5 13:48
+ * @description: 省共享工信行程
+ */
+@Slf4j
+@Service
+public class IrsTripServiceImpl implements IrsTripService {
+    @Autowired
+    private IrsTripServer irsTripServer;
+
+    @Override
+    public String sendMsg(IrsTripSendMsgDTO sendMsgDTO) {
+        IrsTripAuthEnum authEnum = IrsTripAuthEnum.appKeyOf(sendMsgDTO.getAppKey());
+        if(authEnum == null || !authEnum.getAppSecret().equals(sendMsgDTO.getAppSecret())) {
+            return JSON.toJSONString(new IrsTripResultVO<>().setCode(IrsTripResultVO.FAIL_TOKEN_FLAG).setMsg("调用权限校验失败,请检查"));
+        }
+
+        IrsTripTokenVO irsTripTokenVO = irsTripServer.getToken();
+        log.info("请求token返回内容: {}", JSON.toJSONString(irsTripTokenVO));
+        return irsTripServer.sendMsg(sendMsgDTO.getPhone(), irsTripTokenVO.getRequestSecret());
+    }
+
+    @Override
+    public String checkSchedule(IrsTripCheckScheduleDTO checkScheduleDTO) {
+        IrsTripAuthEnum authEnum = IrsTripAuthEnum.appKeyOf(checkScheduleDTO.getAppKey());
+        if(authEnum == null || !authEnum.getAppSecret().equals(checkScheduleDTO.getAppSecret())) {
+            return JSON.toJSONString(new IrsTripResultVO<>().setCode(IrsTripResultVO.FAIL_TOKEN_FLAG).setMsg("调用权限校验失败,请检查"));
+        }
+
+        IrsTripTokenVO irsTripTokenVO = irsTripServer.getToken();
+        return irsTripServer.checkSchedule(checkScheduleDTO.getPhone(), checkScheduleDTO.getVerification(), irsTripTokenVO.getRequestSecret(), checkScheduleDTO.getCity());
+    }
+}

+ 20 - 1
src/main/resources/application-dev.yml

@@ -109,8 +109,11 @@ web.address: http://127.0.0.1:8800/traffic-violation-publicity/
 #定时任务 每5分钟
 cron.face.clear.user: 0 1/5 * * * ?
 
-##场所码
+##场所码,健康码查询信息
 health:
+  appKey: 6f32f95d52c04eb6851b8b0e1a3155ad
+  appSecret: 1b8f230d5904480ca3b6cc737c1211a2
+  queryInfoByBarCodeUrl: https://express.hangzhou-focus.com:8912/health-code/hh/queryInfoByBarCode
   code:
     clientId: szhz_hanghui_company
     clientSecret: dz213d123d3ddsa137812ee6eddcxsaed1
@@ -131,3 +134,19 @@ irs:
   refreshTokenUrl: https://interface.zjzwfw.gov.cn/gateway/app/refreshTokenBySec.htm
   sendMsgUrl: https://interface.zjzwfw.gov.cn/gateway/api/001003001/national/deYxdfMgX8cd8h76.htm
   checkScheduleUrl: https://interface.zjzwfw.gov.cn/gateway/api/001003001/dataSharing/OwQ79dcpSaDW050b.htm
+  health:
+    code:
+      appKey: gjp0ei80uj1vm59m7ydahewtt
+      appSecret: vgn74jpfg5o3gncev1avlceph
+      AppKey: 36022dcb14f2f7d5
+      queryInfoByBarCodeUrl: https://sql.hz.gov.cn/ESBWeb/servlets/33.1111.zjhz.kaEW1bjSV8.SynReq@1.0
+
+#皋亭山景区预约服务
+scenic:
+  configs:
+    - adminId: 81
+      account: 1447686702163296256
+      key: 74d9aed80d7a4c7181ba9615fae0fc11
+  verifyUrl: https://test.d6.com.cn/data/notify/demo/verify
+  queryUrl: https://test.d6.com.cn/data/notify/demo/query
+  consumeUrl: https://test.d6.com.cn/data/notify/demo/consume

+ 19 - 0
src/main/resources/application-prod.yml

@@ -107,6 +107,9 @@ knife4j:
 
 ##场所码
 health:
+  appKey: 6f32f95d52c04eb6851b8b0e1a3155ad
+  appSecret: 1b8f230d5904480ca3b6cc737c1211a2
+  queryInfoByBarCodeUrl: https://express.hangzhou-focus.com:8912/health-code/hh/queryInfoByBarCode
   code:
     clientId: szhz_hanghui_company
     clientSecret: hg2dhfgdkjh348m16fsz13d1das2ca31f
@@ -127,4 +130,20 @@ irs:
   refreshTokenUrl: https://interface.zjzwfw.gov.cn/gateway/app/refreshTokenBySec.htm
   sendMsgUrl: https://interface.zjzwfw.gov.cn/gateway/api/001003001/national/deYxdfMgX8cd8h76.htm
   checkScheduleUrl: https://interface.zjzwfw.gov.cn/gateway/api/001003001/dataSharing/OwQ79dcpSaDW050b.htm
+  health:
+    code:
+      appKey: gjp0ei80uj1vm59m7ydahewtt
+      appSecret: vgn74jpfg5o3gncev1avlceph
+      AppKey: 36022dcb14f2f7d5
+      queryInfoByBarCodeUrl: https://sql.hz.gov.cn/ESBWeb/servlets/33.1111.zjhz.kaEW1bjSV8.SynReq@1.0
+
+#皋亭山景区预约服务
+scenic:
+  configs:
+    - adminId: 117
+      account: 1510813918447009792
+      key: 7e1c0f501eb711eda50a753d19fc4e7b
+  verifyUrl: https://api.d6.com.cn/openapi/v1/verify
+  queryUrl: https://api.d6.com.cn/openapi/v1/query
+  consumeUrl: https://api.d6.com.cn/openapi/v1/consume
 

+ 31 - 0
src/main/resources/application-test.yml

@@ -107,6 +107,9 @@ knife4j:
 
 ##场所码
 health:
+  appKey: 6f32f95d52c04eb6851b8b0e1a3155ad
+  appSecret: 1b8f230d5904480ca3b6cc737c1211a2
+  queryInfoByBarCodeUrl: https://express.hangzhou-focus.com:8912/health-code/hh/queryInfoByBarCode
   code:
     clientId: szhz_hanghui_company
     clientSecret: hg2dhfgdkjh348m16fsz13d1das2ca31f
@@ -127,3 +130,31 @@ irs:
   refreshTokenUrl: https://interface.zjzwfw.gov.cn/gateway/app/refreshTokenBySec.htm
   sendMsgUrl: https://interface.zjzwfw.gov.cn/gateway/api/001003001/national/deYxdfMgX8cd8h76.htm
   checkScheduleUrl: https://interface.zjzwfw.gov.cn/gateway/api/001003001/dataSharing/OwQ79dcpSaDW050b.htm
+  health:
+    code:
+      appKey: gjp0ei80uj1vm59m7ydahewtt
+      appSecret: vgn74jpfg5o3gncev1avlceph
+      AppKey: 36022dcb14f2f7d5
+      queryInfoByBarCodeUrl: https://sql.hz.gov.cn/ESBWeb/servlets/33.1111.zjhz.kaEW1bjSV8.SynReq@1.0
+
+#皋亭山景区预约服务
+#scenic:
+#  configs:
+#    - adminId: 81
+#      account: 1447686702163296256
+#      key: 74d9aed80d7a4c7181ba9615fae0fc11
+#  verifyUrl: https://test.d6.com.cn/data/notify/demo/verify
+#  queryUrl: https://test.d6.com.cn/data/notify/demo/query
+#  consumeUrl: https://test.d6.com.cn/data/notify/demo/consume
+
+
+#皋亭山景区预约服务
+scenic:
+  configs:
+    - adminId: 81
+      account: 1510813918447009792
+      key: 7e1c0f501eb711eda50a753d19fc4e7b
+  verifyUrl: https://api.d6.com.cn/openapi/v1/verify
+  queryUrl: https://api.d6.com.cn/openapi/v1/query
+  consumeUrl: https://api.d6.com.cn/openapi/v1/consume
+