1
0
فهرست منبع

Merge branch 'feature/Eid' of lvjian/wxbase into feature/Eid

完善 wxbase E证通对接开发
吕健 2 سال پیش
والد
کامیت
640d1f91eb

+ 1 - 0
build.gradle

@@ -91,6 +91,7 @@ repositories {
 }
 
 dependencies {
+    implementation 'org.springframework.boot:spring-boot-starter-data-redis:2.4.5'
     implementation 'cn.hutool:hutool-all:5.8.12'
     compileOnly group: 'org.projectlombok', name: 'lombok', version: '1.18.10'
     annotationProcessor group: 'org.projectlombok', name: 'lombok', version: '1.18.10'

+ 2 - 1
src/main/java/com/ematou/wxbase/aop/LogAspect.java

@@ -39,11 +39,12 @@ public class LogAspect {
             logger.info("----------------方法: " + targetMethodName + "的参数为: " + objectMapper.writeValueAsString(joinPoint.getArgs()) + "----------------");
             proceed = joinPoint.proceed();
             logger.info("----------------方法: " + targetMethodName + "执行的返回值为: " + proceed + "----------------");
-        }catch (ServiceException se){
+        } catch (ServiceException se){
             // 如果时业务异常记录结果,抛出交给统一异常处理
             throw se;
         } catch (Throwable throwable) {
             logger.error("----------------方法: " + targetMethodName + "执行出现异常,错误信息为:" + throwable.getMessage() + "----------------", throwable);
+            throw new ServiceException("系统异常,请稍后再试!");
         }
         return proceed;
     }

+ 16 - 0
src/main/java/com/ematou/wxbase/common/constant/RedisKey.java

@@ -0,0 +1,16 @@
+package com.ematou.wxbase.common.constant;
+
+/**
+ * Redis 缓存键前缀
+ *
+ * @author frankeleyn
+ * @email lvjian@qhdswl.com
+ * @date 2023/2/20 16:38
+ */
+public class RedisKey {
+
+    /**
+     * 获取E证通 token 缓存键前缀
+     */
+    public static final String GET_EID_TOKEN = "get_eid_token:";
+}

+ 1 - 1
src/main/java/com/ematou/wxbase/common/web/R.java

@@ -28,7 +28,7 @@ public class R<T> implements Serializable {
     }
 
     public static <T> R<T> success(T data) {
-        return restResult(data, ResponseCodeConstant.code_200, null);
+        return restResult(data, ResponseCodeConstant.code_200, "成功");
     }
 
     public static <T> R<T> error(String message) {

+ 91 - 0
src/main/java/com/ematou/wxbase/config/RedisConfig.java

@@ -0,0 +1,91 @@
+package com.ematou.wxbase.config;
+
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
+import com.fasterxml.jackson.annotation.PropertyAccessor;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.springframework.cache.CacheManager;
+import org.springframework.cache.annotation.EnableCaching;
+import org.springframework.cache.interceptor.KeyGenerator;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.redis.cache.RedisCacheConfiguration;
+import org.springframework.data.redis.cache.RedisCacheManager;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
+import org.springframework.data.redis.serializer.RedisSerializationContext;
+import org.springframework.data.redis.serializer.RedisSerializer;
+import org.springframework.data.redis.serializer.StringRedisSerializer;
+
+import java.lang.reflect.Method;
+import java.time.Duration;
+
+/**
+ * Redis配置类
+ *
+ */
+@Configuration
+@EnableCaching
+public class RedisConfig {
+
+    // 使用默认标签做缓存
+    @Bean
+    public KeyGenerator wiselyKeyGenerator() {
+        return new KeyGenerator() {
+            @Override
+            public Object generate(Object target, Method method, Object... params) {
+                StringBuilder sb = new StringBuilder();
+                sb.append(target.getClass().getName());
+                sb.append(method.getName());
+                for (Object obj : params) {
+                    sb.append(obj.toString());
+                }
+                return sb.toString();
+            }
+        };
+    }
+
+    // 声明模板
+    @Bean
+    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
+        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
+        redisTemplate.setConnectionFactory(redisConnectionFactory);
+        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
+        ObjectMapper objectMapper = new ObjectMapper();
+        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
+        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
+        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
+
+        redisTemplate.setKeySerializer(new StringRedisSerializer());
+        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
+        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
+        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
+
+        redisTemplate.afterPropertiesSet();
+        return redisTemplate;
+    }
+
+    @Bean
+    public CacheManager cacheManager(RedisConnectionFactory factory) {
+        RedisSerializer<String> redisSerializer = new StringRedisSerializer();
+        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
+
+        //解决查询缓存转换异常的问题
+        ObjectMapper om = new ObjectMapper();
+        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
+        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
+        jackson2JsonRedisSerializer.setObjectMapper(om);
+
+        // 配置序列化(解决乱码的问题),过期时间600秒
+        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
+                .entryTtl(Duration.ofSeconds(600))
+                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
+                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
+                .disableCachingNullValues();
+
+        RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
+                .cacheDefaults(config)
+                .build();
+        return cacheManager;
+    }
+}

+ 3 - 12
src/main/java/com/ematou/wxbase/controller/EidController.java

@@ -4,9 +4,7 @@ import com.ematou.wxbase.common.web.R;
 import com.ematou.wxbase.entity.dto.EidTokenRequestDTO;
 import com.ematou.wxbase.service.EidService;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.*;
 
 /**
  * E证通业务 Controller
@@ -22,15 +20,8 @@ public class EidController {
     @Autowired
     private EidService eidService;
 
-    @GetMapping ("/getEidToken")
-    public R getEidToken() {
-        EidTokenRequestDTO requestDTO =
-                EidTokenRequestDTO.builder()
-                        .merchSn("mhbs990053989883052032")
-                        .appCode("Kmall-cw")
-                        .userName("张三")
-                        .idCard("430411196705226476")
-                        .build();
+    @PostMapping("/getEidToken")
+    public R getEidToken(@RequestBody EidTokenRequestDTO requestDTO) {
         return eidService.getEidToken(requestDTO);
     }
 

+ 9 - 0
src/main/java/com/ematou/wxbase/entity/EidTokenRecord.java

@@ -1,6 +1,9 @@
 package com.ematou.wxbase.entity;
 
+import lombok.AllArgsConstructor;
+import lombok.Builder;
 import lombok.Data;
+import lombok.NoArgsConstructor;
 
 import java.sql.Timestamp;
 import java.time.LocalDateTime;
@@ -13,13 +16,19 @@ import java.time.LocalDateTime;
  * @date 2023/2/16 16:20
  */
 @Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
 public class EidTokenRecord {
 
     private String id;// 主键
     private String merchSn;// 商户编号
+    private String appCode;// 应用编码
     private String token;// token
     private LocalDateTime generationTime;// 生成时间
     private LocalDateTime expireTime;// 过期时间
+    private String idCard;// 身份证
+    private String userName;// 用户姓名
     private Timestamp tstm;// 时间戳
 
 }

+ 3 - 1
src/main/java/com/ematou/wxbase/entity/dto/EidTokenRequestDTO.java

@@ -5,6 +5,8 @@ import lombok.Builder;
 import lombok.Data;
 import lombok.NoArgsConstructor;
 
+import java.io.Serializable;
+
 /**
  * E证通请求 DTO
  *
@@ -16,7 +18,7 @@ import lombok.NoArgsConstructor;
 @AllArgsConstructor
 @NoArgsConstructor
 @Builder
-public class EidTokenRequestDTO {
+public class EidTokenRequestDTO implements Serializable {
 
     /**
      * 商户号

+ 38 - 0
src/main/java/com/ematou/wxbase/entity/vo/EidTokenResponseVO.java

@@ -0,0 +1,38 @@
+package com.ematou.wxbase.entity.vo;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * E证通响应 VO
+ *
+ * @author frankeleyn
+ * @email lvjian@qhdswl.com
+ * @date 2023/2/20 14:11
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class EidTokenResponseVO implements Serializable {
+
+    /**
+     * 核验人姓名
+     */
+    private String userName;
+
+    /**
+     * 核验人身份证
+     */
+    private String idCard;
+
+    /**
+     * 核验人E证通 token
+     */
+    private String eidToken;
+
+}

+ 1 - 1
src/main/java/com/ematou/wxbase/handler/GlobalExceptionHandler.java

@@ -26,7 +26,7 @@ public class GlobalExceptionHandler {
     public R handleServiceException(ServiceException e, HttpServletRequest request)
     {
         String requestURI = request.getRequestURI();
-        log.error("请求地址'{}',发生业务异常{}", requestURI, e.getMessage());
+        log.error("请求地址【{}】,发生业务异常【{}】", requestURI, e.getMessage());
         return R.error(e.getMessage());
     }
 

+ 3 - 1
src/main/java/com/ematou/wxbase/service/EidService.java

@@ -2,6 +2,7 @@ package com.ematou.wxbase.service;
 
 import com.ematou.wxbase.common.web.R;
 import com.ematou.wxbase.entity.dto.EidTokenRequestDTO;
+import com.ematou.wxbase.entity.vo.EidTokenResponseVO;
 
 /**
  * E证通业务接口
@@ -16,10 +17,11 @@ public interface EidService {
      * 获取E证通 Token
      * @return
      */
-    R<String> getEidToken(EidTokenRequestDTO requestDTO);
+    R<EidTokenResponseVO> getEidToken(EidTokenRequestDTO requestDTO);
 
     /**
      * 获取E证通结果
      */
     R getEidResult();
+
 }

+ 88 - 19
src/main/java/com/ematou/wxbase/service/impl/EidServiceImpl.java

@@ -1,15 +1,16 @@
 package com.ematou.wxbase.service.impl;
 
+import cn.hutool.core.util.StrUtil;
+import com.ematou.wxbase.common.constant.RedisKey;
 import com.ematou.wxbase.common.constant.TokenType;
 import com.ematou.wxbase.common.web.R;
-import com.ematou.wxbase.entity.EidMerch;
-import com.ematou.wxbase.entity.MerchApp;
-import com.ematou.wxbase.entity.MerchInfo;
-import com.ematou.wxbase.entity.OperationRecord;
+import com.ematou.wxbase.entity.*;
 import com.ematou.wxbase.entity.dto.EidTokenRequestDTO;
+import com.ematou.wxbase.entity.vo.EidTokenResponseVO;
 import com.ematou.wxbase.exception.Assert;
 import com.ematou.wxbase.exception.ServiceException;
 import com.ematou.wxbase.mapper.EidMerchMapper;
+import com.ematou.wxbase.mapper.EidTokenRecordMapper;
 import com.ematou.wxbase.mapper.MerchAppMapper;
 import com.ematou.wxbase.mapper.MerchInfoMapper;
 import com.ematou.wxbase.service.EidService;
@@ -22,11 +23,14 @@ import com.tencentcloudapi.faceid.v20180301.FaceidClient;
 import com.tencentcloudapi.faceid.v20180301.models.GetEidTokenRequest;
 import com.tencentcloudapi.faceid.v20180301.models.GetEidTokenResponse;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.stereotype.Service;
 
 import javax.annotation.Resource;
+import java.time.Duration;
+import java.time.LocalDateTime;
 import java.util.List;
-import java.util.UUID;
 
 /**
  * E证通业务实现类
@@ -51,6 +55,11 @@ public class EidServiceImpl implements EidService {
     @Resource
     private OperationRecordService recordService;
 
+    @Resource
+    private EidTokenRecordMapper eidTokenRecordMapper;
+
+    @Autowired
+    private RedisTemplate<String, Object> redisTemplate;
 
     /**
      * 获取E证通 Token
@@ -58,7 +67,7 @@ public class EidServiceImpl implements EidService {
      * @return
      */
     @Override
-    public R<String> getEidToken(EidTokenRequestDTO requestDTO) {
+    public R<EidTokenResponseVO> getEidToken(EidTokenRequestDTO requestDTO) {
         // 校验参数
         validateParam(requestDTO);
         // 校验商户
@@ -68,14 +77,43 @@ public class EidServiceImpl implements EidService {
         // 查询E证通商户配置
         EidMerch eidMerch = eidMerchMapper.selectByMerchSn(requestDTO.getMerchSn());
         Assert.notNull(eidMerch, "商户E证通配置不存在!");
-        // 请求E证通 token
-        String eidToken = reqEidToken(eidMerch);
-        // 插入操作记录
-        insertOptRecord(requestDTO);
-        // TODO: 插入E证通 token 记录
 
+        // 响应数据
+        EidTokenResponseVO responseVO =
+                EidTokenResponseVO.builder()
+                        .idCard(requestDTO.getIdCard())
+                        .userName(requestDTO.getUserName())
+                        .build();
 
-        return R.success(null);
+        // 获取E证通缓存键(功能名称:商户号:应用编码:核验人身份证号),例如: get_eid_token:merch123:kmall-pt:431422196503217896
+        String cacheKey =
+                RedisKey.GET_EID_TOKEN +
+                        requestDTO.getMerchSn() + ":" +
+                        requestDTO.getAppCode() + ":" +
+                        requestDTO.getIdCard();
+        // 获取缓存中的 token
+        String cacheEidToken = (String) redisTemplate.opsForValue().get(cacheKey);
+        if (StrUtil.isNotBlank(cacheEidToken)) {
+            // 如果不为空,缓存存在
+            // 将 token 组装到响应数据中
+            responseVO.setEidToken(cacheEidToken);
+        }else {
+            // 如果为空,缓存不存在
+            // 请求E证通 token
+            //String eidToken = reqEidToken(eidMerch, requestDTO.getUserName(), requestDTO.getIdCard());
+            String eidToken = "1a2b3c4d5e6f";
+            // 将E证通 token 设置进 Redis,并设置过期时间
+            redisTemplate.opsForValue().set(cacheKey, eidToken, Duration.ofSeconds(eidMerch.getTokenExpired()));
+            // 插入操作记录
+            insertOptRecord(requestDTO);
+            // 插入E证通操作记录
+            insertEidTokenRecord(requestDTO, eidToken);
+            // 返回结果组装
+            responseVO.setEidToken(eidToken);
+        }
+
+        // 返回结果
+        return R.success(responseVO);
     }
 
 
@@ -127,10 +165,13 @@ public class EidServiceImpl implements EidService {
 
     /**
      * 获取E证通 token
-     * @param eidMerch
+     *
+     * @param eidMerch E证通商户配置
+     * @param userName 核验人姓名
+     * @param idCard 核验人身份证
      * @return
      */
-    private String reqEidToken(EidMerch eidMerch) {
+    private String reqEidToken(EidMerch eidMerch, String userName, String idCard) {
         try {
             // 创建访问凭据
             Credential cred = new Credential(eidMerch.getSecretId(), eidMerch.getSecretKey());
@@ -144,19 +185,23 @@ public class EidServiceImpl implements EidService {
             FaceidClient client = new FaceidClient(cred, "", clientProfile);
             // 实例化一个请求对象,每个接口都会对应一个request对象
             GetEidTokenRequest req = new GetEidTokenRequest();
-
+            // 设置商户 id
+            req.setMerchantId(eidMerch.getMerchId());
+            // 设置核验人姓名
+            req.setName(userName);
+            // 设置核验人身份证
+            req.setIdCard(idCard);
             // 返回的resp是一个GetEidTokenResponse的实例,与请求对象对应
             GetEidTokenResponse resp = client.GetEidToken(req);
             // 输出json格式的字符串回包
-            System.out.println(GetEidTokenResponse.toJsonString(resp));
+            log.info("E证通返回结果: " + GetEidTokenResponse.toJsonString(resp));
             // 获取 EidToken
             String eidToken = resp.getEidToken();
-            eidToken = UUID.randomUUID().toString().replace("-", "");
             // 返回 token
             return eidToken;
         } catch (TencentCloudSDKException e) {
-            log.error("腾讯云 SDK 人脸核验异常.", e);
-            throw new ServiceException("人脸核验异常!");
+            log.error("腾讯云 SDK 请求E证通 token 异常.", e);
+            throw new ServiceException("请求E证通异常!");
         }
 
     }
@@ -177,4 +222,28 @@ public class EidServiceImpl implements EidService {
         recordService.insertRecord(operationRecord);
     }
 
+    /**
+     * 插入E证通 token 记录
+     *
+     * @param requestDTO
+     * @param eidToken
+     */
+    private void insertEidTokenRecord(EidTokenRequestDTO requestDTO, String eidToken) {
+        // token 生成时间
+        LocalDateTime generationTime = LocalDateTime.now();
+        // 过期时间, 生成时间 + 60s
+        LocalDateTime expireTime = generationTime.plusMinutes(8L);
+        EidTokenRecord eidTokenRecord = EidTokenRecord.builder()
+                .merchSn(requestDTO.getMerchSn())
+                .appCode(requestDTO.getAppCode())
+                .token(eidToken)
+                .generationTime(generationTime)
+                .expireTime(expireTime)
+                .idCard(requestDTO.getIdCard())
+                .userName(requestDTO.getUserName())
+                .build();
+        int insert = eidTokenRecordMapper.insert(eidTokenRecord);
+        Assert.notTrue(insert < 1, "插入E证通 token 记录失败!");
+    }
+
 }

+ 8 - 0
src/main/resources/application.yml

@@ -7,12 +7,20 @@ wx:
     appSecret: 78413a82d0332ecbf7fdf475d0a8b08e
     grantType: client_credential
     url: https://api.weixin.qq.com/cgi-bin/token?grant_type=%s&appid=%s&secret=%s
+
 spring:
   datasource:
     driver-class-name: com.mysql.cj.jdbc.Driver
     username: wx_base
     password: goZ7ooGmxV
     url: jdbc:mysql://out-rm-wz92efl25x02n44xego.mysql.rds.aliyuncs.com:3306/wx_base?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
+  # redis 配置
+  redis:
+    database: 1
+    host: 120.78.152.8
+    port: 6379
+    password: Abc-123#*.-
+
 mybatis:
   mapper-locations: classpath:mybatis/*.xml
   configuration:

+ 25 - 0
src/main/resources/mybatis/EidMerchMapper.xml

@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ematou.wxbase.mapper.EidMerchMapper">
+
+    <sql id="eidMerchColumns">
+        t.id,
+        t.merch_sn,
+        t.secret_id,
+        t.secret_key,
+        t.merch_id,
+        t.token_expired
+    </sql>
+
+    <!-- 根据商户号查询商户E证通配置信息 -->
+    <select id="selectByMerchSn" resultType="com.ematou.wxbase.entity.EidMerch">
+        SELECT
+            <include refid="eidMerchColumns" />
+        FROM
+            `eid_merch` t
+        WHERE t.merch_sn = #{merchSn};
+    </select>
+
+</mapper>

+ 6 - 1
src/test/java/com/ematou/wxbase/EidTokenTest.java

@@ -26,6 +26,7 @@ public class EidTokenTest {
 
     private final static String secretId = "AKIDIRYotrdCZnqkT0LTVzJSQFrGmLoALLJA";
     private final static String secretKey = "jICxWrJchWBg8RYO2RZYLwMXh0bR6imd";
+    private final static String merchId = "0NSJ2302171312018748";
 
     @Test
     public void getEidToken() {
@@ -42,11 +43,15 @@ public class EidTokenTest {
             FaceidClient client = new FaceidClient(cred, "", clientProfile);
             // 实例化一个请求对象,每个接口都会对应一个request对象
             GetEidTokenRequest req = new GetEidTokenRequest();
-
+            // 设置商户 id
+            req.setMerchantId(merchId);
+            req.setIdCard("431422196503217896");
+            req.setName("张三");
             // 返回的resp是一个GetEidTokenResponse的实例,与请求对象对应
             GetEidTokenResponse resp = client.GetEidToken(req);
             // 输出json格式的字符串回包
             System.out.println(GetEidTokenResponse.toJsonString(resp));
+            resp.getEidToken();
         } catch (TencentCloudSDKException e) {
             log.error("腾讯云 SDK Error", e);
         }

+ 40 - 0
src/test/java/com/ematou/wxbase/RedisTest.java

@@ -0,0 +1,40 @@
+package com.ematou.wxbase;
+
+import cn.hutool.core.util.StrUtil;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.data.redis.core.RedisTemplate;
+
+import java.time.Duration;
+
+/**
+ * Redis 测试类
+ *
+ * @author frankeleyn
+ * @email lvjian@qhdswl.com
+ * @date 2023/2/20 15:35
+ */
+@SpringBootTest
+public class RedisTest {
+
+    @Autowired
+    private RedisTemplate<String, Object> redisTemplate;
+
+    @Test
+    public void testSet() {
+        redisTemplate.opsForValue().set("get_eid_token:merch123:kmall-pt:431422196503217896", "1234556", Duration.ofSeconds(60L));
+    }
+
+    @Test
+    public void testDelete() {
+        redisTemplate.delete("lj_hh");
+    }
+
+    @Test
+    public void testGet() {
+        String o = (String) redisTemplate.opsForValue().get("get_eid_token:merch123:kmall-pt:431422196503217896");
+        System.out.println("Redis 取值 => " + o + " 是否为空 =》 " + StrUtil.isNotBlank(o));
+    }
+
+}