Ver Fonte

加密手机号码解密

yuhongqi há 1 mês atrás
pai
commit
c0e2d9b6a0

+ 2 - 0
fs-service-system/src/main/java/com/fs/live/domain/LiveOrder.java

@@ -389,5 +389,7 @@ public class LiveOrder extends BaseEntity {
     private Long recordId;
     private Long attrValueId;
 
+    /** 地址ID(仅用于参数传递,不映射数据库) */
+    private Long addressId;
 
 }

+ 49 - 0
fs-service-system/src/main/java/com/fs/live/service/impl/LiveOrderServiceImpl.java

@@ -37,6 +37,7 @@ import com.fs.common.event.TemplateEvent;
 import com.fs.common.event.TemplateListenEnum;
 import com.fs.common.exception.CustomException;
 import com.fs.common.utils.*;
+import com.fs.live.utils.CrossServiceRsaUtil;
 import com.fs.company.domain.Company;
 import com.fs.company.domain.CompanyDept;
 import com.fs.company.domain.CompanyMoneyLogs;
@@ -2901,6 +2902,31 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
         });
     }
 
+    private void getPhoneByHttp(LiveOrder liveOrder) {
+        try {
+            String encryptedStr = CrossServiceRsaUtil.encryptForRequest("phone");
+            String url = "http://42.194.245.189:8010/app/common/getEncryptedPhone?encryptedStr="
+                    + java.net.URLEncoder.encode(encryptedStr, "UTF-8")
+                    + "&addressId=" + liveOrder.getAddressId();
+            org.springframework.web.client.RestTemplate restTemplate = new org.springframework.web.client.RestTemplate();
+            String responseBody = restTemplate.getForObject(url, String.class);
+            if (StringUtils.isNotEmpty(responseBody)) {
+                com.alibaba.fastjson.JSONObject respObj = com.alibaba.fastjson.JSON.parseObject(responseBody);
+                if (respObj != null && "200".equals(respObj.getString("code"))) {
+                    String encryptedPhone = respObj.getString("data");
+                    if (StringUtils.isNotEmpty(encryptedPhone)) {
+                        String realPhone = CrossServiceRsaUtil.decryptResponse(encryptedPhone);
+                        if (StringUtils.isNotEmpty(realPhone)) {
+                            liveOrder.setUserPhone(realPhone);
+                        }
+                    }
+                }
+            }
+        } catch (Exception ex) {
+            log.error("请求his_java获取加密手机号失败, userId:{}, addressId:{}", liveOrder.getUserId(), liveOrder.getAddressId(), ex);
+        }
+    }
+
     @Override
     public R createLiveOrderTest(LiveOrder liveOrder) {
         String orderKey = redisCache.getCacheObject("orderKey:" + liveOrder.getOrderKey());
@@ -3136,6 +3162,8 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
         if (liveOrder.getUserAddress() == null) return R.error("用户地址不能为空");
         if (liveOrder.getTotalNum() == null) return R.error("商品数量不能为空");
 
+
+
         Live live = liveService.selectLiveByLiveId(liveOrder.getLiveId());
         if (live == null) return R.error("当前直播不存在");
         FsStoreProduct fsStoreProduct = fsStoreProductService.selectFsStoreProductById(liveOrder.getProductId());
@@ -3192,6 +3220,27 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
 //            log.error("插入 liveGoodsStock 队列时被中断: {}", e.getMessage(), e);
 //        }
 
+        // 判断用户手机号是否为脱敏格式(如177****5248),如果是则尝试替换为真实加密手机号
+        if (liveOrder.getUserPhone().contains("****")) {
+            try {
+                FsUser fsUser = userService.selectFsUserById(Long.parseLong(liveOrder.getUserId()));
+                if (fsUser != null && StringUtils.isNotEmpty(fsUser.getPhone())) {
+                    // fs_user的phone是AES加密存储的,脱敏后与liveOrder的脱敏手机号比较
+                    String maskedPhone = ParseUtils.parsePhone(fsUser.getPhone());
+                    if (maskedPhone != null && maskedPhone.equals(liveOrder.getUserPhone())) {
+                        // 脱敏格式匹配,用fs_user的加密phone替换
+                        liveOrder.setUserPhone(fsUser.getPhone());
+                    } else if (liveOrder.getAddressId() != null) {
+                        getPhoneByHttp(liveOrder);
+                    }
+                } else if (liveOrder.getAddressId() != null) {
+                    // fs_user查不到,但有addressId,发起请求③
+                    getPhoneByHttp(liveOrder);
+                }
+            } catch (Exception e) {
+                log.error("处理脱敏手机号替换异常, userId:{}", liveOrder.getUserId(), e);
+            }
+        }
         //判断是否是三种特定产品
         if (fsStoreProduct.getProductId() != null && (fsStoreProduct.getProductId().equals(3168L)
                 || fsStoreProduct.getProductId().equals(3184L)

+ 85 - 0
fs-service-system/src/main/java/com/fs/live/utils/CrossServiceRsaUtil.java

@@ -0,0 +1,85 @@
+package com.fs.live.utils;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.PostConstruct;
+import javax.crypto.Cipher;
+import java.security.KeyFactory;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.Base64;
+
+/**
+ * 跨服务RSA加密通信工具类
+ * 用于rt_fhyx_java与his_java之间的安全通信
+ *
+ * 请求方向: rt_fhyx_java -> his_java
+ *   rt_fhyx_java用his_java公钥(PUB_A)加密请求参数,his_java用私钥(PRI_A)解密验证
+ *
+ * 响应方向: his_java -> rt_fhyx_java
+ *   his_java用rt_fhyx_java公钥(PUB_B)加密响应数据,rt_fhyx_java用私钥(PRI_B)解密
+ */
+@Slf4j
+@Component
+public class CrossServiceRsaUtil {
+
+    @Value("${cross-service-rsa.his-public-key:}")
+    private String hisPublicKeyStr;
+
+    @Value("${cross-service-rsa.self-private-key:}")
+    private String selfPrivateKeyStr;
+
+    private static String staticHisPublicKey;
+    private static String staticSelfPrivateKey;
+
+    @PostConstruct
+    public void init() {
+        staticHisPublicKey = hisPublicKeyStr;
+        staticSelfPrivateKey = selfPrivateKeyStr;
+        log.info("CrossServiceRsaUtil 初始化完成,his-public-key长度: {}, self-private-key长度: {}",
+                hisPublicKeyStr != null ? hisPublicKeyStr.length() : 0,
+                selfPrivateKeyStr != null ? selfPrivateKeyStr.length() : 0);
+    }
+
+    /**
+     * 使用his_java公钥加密请求参数
+     * @param plainText 明文
+     * @return Base64编码的密文
+     */
+    public static String encryptForRequest(String plainText) {
+        try {
+            PublicKey publicKey = KeyFactory.getInstance("RSA")
+                    .generatePublic(new X509EncodedKeySpec(Base64.getDecoder().decode(staticHisPublicKey)));
+            Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
+            cipher.init(Cipher.ENCRYPT_MODE, publicKey);
+            byte[] encryptedBytes = cipher.doFinal(plainText.getBytes("UTF-8"));
+            return Base64.getEncoder().encodeToString(encryptedBytes);
+        } catch (Exception e) {
+            log.error("RSA请求加密失败", e);
+            throw new RuntimeException("RSA请求加密失败: " + e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 使用自身私钥解密响应数据
+     * @param encryptedText Base64编码的密文
+     * @return 解密后的明文
+     */
+    public static String decryptResponse(String encryptedText) {
+        try {
+            PrivateKey privateKey = KeyFactory.getInstance("RSA")
+                    .generatePrivate(new PKCS8EncodedKeySpec(Base64.getDecoder().decode(staticSelfPrivateKey)));
+            Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
+            cipher.init(Cipher.DECRYPT_MODE, privateKey);
+            byte[] decryptedBytes = cipher.doFinal(Base64.getDecoder().decode(encryptedText));
+            return new String(decryptedBytes, "UTF-8");
+        } catch (Exception e) {
+            log.error("RSA响应解密失败", e);
+            throw new RuntimeException("RSA响应解密失败: " + e.getMessage(), e);
+        }
+    }
+}

+ 7 - 0
fs-service-system/src/main/resources/application-config.yml

@@ -141,3 +141,10 @@ tmp_secret_config:
   app_id: 1319721001
   region: ap-chongqing
   proxy: fs
+
+# 跨服务RSA加密通信配置(rt_fhyx_java <-> his_java)
+# 请求方向: rt用his公钥(PUB_A)加密,his用私钥(PRI_A)解密
+# 响应方向: his用rt公钥(PUB_B)加密,rt用私钥(PRI_B)解密
+cross-service-rsa:
+  his-public-key: MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwIaEQAB1BefLbvzRFk9Gi7cLQwU3HyLKLktEyn8x6hn1bkaL6FxsE31j7XhoXHflnXLymQ6gk1gCZWS/pIBTuWFQVN4SShBS2r0IgERV2EA54VtKU7VwrVzTdFdKG8Oh4qZUDYvUdb0QtRhd2KS55IOK5cV4oxsGDIXCj6imwfJQcdEB8irOqgQI1nSC3XaKuTicD4gsAyJX65tBrLpfGao9dv9TClX+W9MfCONTXeYwo5fK4FpFgVVsi79Vzp6tOgD9sTRM+B7Ll6SFh59VOAv3m4TECdDYcdAjwikTmbQtdRhRX90dNHxZ6jSqRUPOjQs/rEvI5RsbYNsv+d4bzQIDAQAB
+  self-private-key: MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDBvIvSdPHmKxOSzd7oEjmzp7A57RALjpKvrtmy1xhJMLcz/pAKj9k0XEAB1Ls4xZSsQH3NnSHLRF82hffeWEaoTvWTpxQ4Pd+l8MUnEqzaLKrOxYi2j8k2dB72y5wLq/9ZfJqvkT0XzD6xP4ZA1WHRZXPEc0BANOQS5BTdKW+pQ59AWgLlgisrd9+taZ6PoP+8j6B3Plgh9x5RqtmQoNdlGCZeaFJQgADTi1z/Z+A1dzWNbNL7LZx98CMTVwXAwuw/JZ1+gDZitNkZA8hi6o/2yksktdkaRJ+2Myuo2kDy+B3FqG5RHLuQCqJBH0lCUkJJSBD3JxgB7IhUvamk8LklAgMBAAECggEAHRRc9GkDSiYbGQT3uWPK73FzHS69Is7yq5YWLSbmCU5boV1LZ5px4s8Z+Gxi8cGOcFB50ZwNT4JivD9dli0v5eF8MP56JXr8D99U6GmAMntT5Bd4TtbEP/0sDKVBNetKN7dhTIsDXYgVvOspDqA4v4J33vvhoAGQXlmH3C51CTXOyTiowKmHyxV8FC6pV42fl7fKl5v4U3SN0GPJbw8kAJzq84zTf8zso5APTWfurXQL3QN49YinE4xsR0gg5zX1DrZI2alAQkhH8Z/cXL93u91EcqlqRpmNPcpbSGgmvsnZR+RyUH0Tuw1hv3yArsnX/YSmURxNAGoSnzLNkhjmXQKBgQD+AnxfsY1iHuMNC06GDwTqnl0e2ibw5HGyPpFelXrNULiejI+qYsw7VExHXFBq7b6oMzJA1vNZhc0C0mif/1XCFM7bUZ2rziHwWqBa/M5vhu752ymL3k8cxy+kJTrxA+2rRXfRMNsBRhftGWVuflCkp5vPVaSshUwMq62Qgsu52wKBgQDDQSjFIVDE3omM0ainV/X3XQklUkiDOzVJYsUdUvB0NnDfvkgrGaVlCelCnuvxiSB8C3iJAcFxADe93STV1TwGKBDecGOY7o0tjdu8zxHi/DSFNRi62QT//zzBMJ6vBxkE3hegCtrL2hEv6iwYq2z/USMqSx576l51bv3pzEdI/wKBgQD3HFn9CpF+DIhcr0xEFSZ2TXxQQHCz17pYapAn0Qo35bjF3f8CBr2jVl4i6kb9z660maHg/Hyf8hBBF4tbZB79Ahs/uEXI12+jEbeA7QKz8zRX9IOVo7+ZQpoxeVq/EFBOV/W5mGrm8Vbjfdp0xQQVO03URgA/KiqTBQ2EUgp6EwKBgQCtOPuQmBPt9HgXhFGZ1QGUKOfaQnuuYTLPHl89ur00fK/67/datW/0iax1vEQajstRAWQ4OGSDeev5912cj4am95ivnwndPZXmWjod8Z8uvw1hOE/uRGaZLrmZsya0pRlfJiHTjI272ITy+0+Pu1YB1Nvbw7URvkV67bM6Xk7HxQKBgQCqXxq+gLsCqwOITHdWtP5uhfXfTzfelC8U6rsMMhO8kZ9ztuVpeUQF32MtqzHccvdjNB5ixshmGNXBaJbxSFYOfHEqpg5tuo5Oq4mUomO4NW3ylzTJvIpTSJ6FX0mdPO5qI0p4mOZLCvqaGJrKz6iPP0Ws5mozHqBl/TDFgdhKJg==