浏览代码

补贴项目

Newspaper 1 月之前
父节点
当前提交
8f3f20cc3a

+ 1 - 1
app-admin/src/main/java/com/ruoyi/web/domain/entity/SubsidyProjects.java

@@ -49,7 +49,7 @@ public class SubsidyProjects implements Serializable {
     /**
      * 银行账号SHA-256哈希(加密存储)
      */
-    private String bankAccountHash;
+    private String bankAccount;
 
     /**
      * 兑现时间

+ 14 - 2
app-admin/src/main/java/com/ruoyi/web/service/PersonInfoService.java

@@ -7,8 +7,6 @@ import com.ruoyi.web.domain.dto.person.PersonInfoQueryRequest;
 import com.ruoyi.web.domain.entity.PersonInfo;
 import com.ruoyi.web.domain.vo.PersonInfoVO;
 
-import java.util.List;
-
 /**
  *
  */
@@ -27,4 +25,18 @@ public interface PersonInfoService extends IService<PersonInfo> {
      * @return
      */
     QueryWrapper<PersonInfo> getQueryWrapper(PersonInfoQueryRequest personInfoQueryRequest) ;
+
+    /**
+     * 解密身份证
+     * @param idCard
+     * @return
+     */
+    String decryptIdCard(String idCard);
+
+    /**
+     * 加密身份证
+     * @param idCard
+     * @return
+     */
+    String encryptIdCard(String idCard);
 }

+ 8 - 0
app-admin/src/main/java/com/ruoyi/web/service/impl/HouseInfoServiceImpl.java

@@ -7,6 +7,7 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.ruoyi.common.utils.SecureSensitiveUtils;
 import com.ruoyi.common.utils.bean.BeanUtils;
 import com.ruoyi.web.domain.dto.house.HouseInfoQueryRequest;
 import com.ruoyi.web.domain.entity.HouseInfo;
@@ -74,6 +75,13 @@ public class HouseInfoServiceImpl extends ServiceImpl<HouseInfoMapper, HouseInfo
             if (houseOwnerId != null) {
                 PersonInfo houseOwnerInfo = personInfoService.getById(houseOwnerId);
                 BeanUtils.copyProperties(houseOwnerInfo, houseOwnerInfoVO);
+                // 获取 房主 身份证 解密 脱敏
+                String idCard = houseOwnerInfoVO.getIdCard();
+                idCard = personInfoService.decryptIdCard(idCard);
+                houseOwnerInfoVO.setIdCard(SecureSensitiveUtils.maskIdCard(idCard));
+                // 手机号脱敏
+                String phone = houseOwnerInfoVO.getPhone();
+                houseOwnerInfoVO.setPhone(SecureSensitiveUtils.maskPhone(phone));
                 houseInfoVO.setHouseOwnerInfo(houseOwnerInfoVO);
             }
             return houseInfoVO;

+ 39 - 6
app-admin/src/main/java/com/ruoyi/web/service/impl/PersonInfoServiceImpl.java

@@ -6,24 +6,20 @@ import cn.hutool.core.util.StrUtil;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
-
+import com.ruoyi.common.utils.SecureSensitiveUtils;
 import com.ruoyi.common.utils.bean.BeanUtils;
 import com.ruoyi.web.domain.dto.person.PersonInfoQueryRequest;
 import com.ruoyi.web.domain.entity.HouseInfo;
 import com.ruoyi.web.domain.entity.HouseholdInfo;
 import com.ruoyi.web.domain.entity.PersonInfo;
+import com.ruoyi.web.domain.vo.PersonInfoVO;
 import com.ruoyi.web.mapper.HouseInfoMapper;
 import com.ruoyi.web.mapper.PersonInfoMapper;
-import com.ruoyi.web.service.HouseInfoService;
 import com.ruoyi.web.service.HouseholdInfoService;
 import com.ruoyi.web.service.PersonInfoService;
-import com.ruoyi.web.domain.vo.PersonInfoVO;
-import org.checkerframework.checker.units.qual.A;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
-import java.util.ArrayList;
-import java.util.Date;
 import java.util.List;
 import java.util.stream.Collectors;
 
@@ -61,6 +57,13 @@ public class PersonInfoServiceImpl extends ServiceImpl<PersonInfoMapper, PersonI
         List<PersonInfoVO> viewList = personInfoPage.getRecords().stream().map(personInfo -> {
             PersonInfoVO personInfoVO = new PersonInfoVO();
             BeanUtils.copyProperties(personInfo, personInfoVO);
+            // 获取身份证号 解密 -> 脱敏
+            String idCard = personInfoVO.getIdCard();
+            idCard= this.decryptIdCard(idCard);
+            personInfoVO.setIdCard(SecureSensitiveUtils.maskIdCard(idCard));
+            // 手机号脱敏
+            String phone = personInfoVO.getPhone();
+            personInfoVO.setPhone(SecureSensitiveUtils.maskPhone(phone));
 
             // 联表查询补充数据
             if (personInfo.getHouseholdId() != null) {
@@ -143,6 +146,36 @@ public class PersonInfoServiceImpl extends ServiceImpl<PersonInfoMapper, PersonI
         queryWrapper.orderBy(StrUtil.isNotEmpty(sortField), sortOrder.equals("ascend"), sortField);
         return queryWrapper;
     }
+
+    /**
+     * 解密身份证
+     * @param idCard
+     * @return
+     */
+    @Override
+    public String decryptIdCard(String idCard) {
+        try {
+            idCard = SecureSensitiveUtils.decrypt(idCard, SecureSensitiveUtils.generateAesKey());
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+        return idCard;
+    }
+
+    /**
+     * 加密身份证
+     * @param idCard
+     * @return
+     */
+    @Override
+    public String encryptIdCard(String idCard) {
+        try {
+            idCard = SecureSensitiveUtils.encrypt(idCard, SecureSensitiveUtils.generateAesKey());
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+        return idCard;
+    }
 }
 
 

+ 29 - 1
app-admin/src/main/java/com/ruoyi/web/service/impl/SubsidyProjectsServiceImpl.java

@@ -5,14 +5,18 @@ import cn.hutool.core.util.StrUtil;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.ruoyi.common.utils.SecureSensitiveUtils;
 import com.ruoyi.web.domain.dto.SubsidyProjects.SubsidyProjectsQueryRequest;
 import com.ruoyi.web.domain.entity.SubsidyProjects;
 import com.ruoyi.web.mapper.SubsidyProjectsMapper;
+import com.ruoyi.web.service.PersonInfoService;
 import com.ruoyi.web.service.SubsidyProjectsService;
 import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
 import java.util.Date;
+import java.util.List;
 
 /**
  *
@@ -21,6 +25,10 @@ import java.util.Date;
 public class SubsidyProjectsServiceImpl extends ServiceImpl<SubsidyProjectsMapper, SubsidyProjects>
         implements SubsidyProjectsService {
 
+    @Autowired
+    private PersonInfoService personInfoService;
+
+
     /**
      * 分页获取补贴-项目列表
      *
@@ -31,8 +39,28 @@ public class SubsidyProjectsServiceImpl extends ServiceImpl<SubsidyProjectsMappe
     public Page<SubsidyProjects> getListSubsidyProjectsByPage(SubsidyProjectsQueryRequest subsidyProjectsQueryRequest) {
         long current = subsidyProjectsQueryRequest.getCurrent();
         long size = subsidyProjectsQueryRequest.getPageSize();
-        return this.page(new Page<>(current, size),
+        Page<SubsidyProjects> page = this.page(new Page<>(current, size),
                 getQueryWrapper(subsidyProjectsQueryRequest));
+        // 处理每条记录
+        List<SubsidyProjects> records = page.getRecords();
+        if (records != null) {
+            records.forEach(subsidyProjects -> {
+                // 解密-> 身份证 & 银行号码 -> 脱敏
+                String bankAccount = null;
+                String idCard = null;
+                try {
+                    // 解密
+                    idCard = personInfoService.decryptIdCard(subsidyProjects.getIdCard());
+                    bankAccount = SecureSensitiveUtils.decrypt(subsidyProjects.getBankAccount(), SecureSensitiveUtils.generateAesKey());
+                    // 脱敏
+                    subsidyProjects.setIdCard(SecureSensitiveUtils.maskIdCard(idCard));
+                    subsidyProjects.setBankAccount(SecureSensitiveUtils.maskBankAccount(bankAccount));
+                } catch (Exception e) {
+                    throw new RuntimeException(e.getMessage());
+                }
+            });
+        }
+        return page;
     }
 
     /**

+ 1 - 1
app-admin/src/main/resources/mapper/web/HouseholdInfoMapper.xml

@@ -8,7 +8,7 @@
             <id property="id" column="id" jdbcType="INTEGER"/>
             <result property="householdCode" column="household_code" jdbcType="VARCHAR"/>
             <result property="householdHead" column="household_head" jdbcType="VARCHAR"/>
-            <result property="householdHeadIdCard" column="household_head_id_card" jdbcType="VARCHAR"/>
+            <result property="householdHeadIdCard" column="household_head_id_card" jdbcType="CHAR"/>
             <result property="householdType" column="household_type" jdbcType="TINYINT"/>
             <result property="householdAddress" column="household_address" jdbcType="VARCHAR"/>
             <result property="belongingArea" column="belonging_area" jdbcType="VARCHAR"/>

+ 1 - 1
app-admin/src/main/resources/mapper/web/PersonInfoMapper.xml

@@ -7,7 +7,7 @@
     <resultMap id="BaseResultMap" type="com.ruoyi.web.domain.entity.PersonInfo">
         <id property="id" column="id" jdbcType="INTEGER"/>
         <result property="realname" column="realname" jdbcType="VARCHAR"/>
-        <result property="idCard" column="id_card" jdbcType="VARCHAR"/>
+        <result property="idCard" column="id_card" jdbcType="CHAR"/>
         <result property="ethnic" column="ethnic" jdbcType="VARCHAR"/>
         <result property="age" column="age" jdbcType="TINYINT"/>
         <result property="gender" column="gender" jdbcType="TINYINT"/>

+ 117 - 0
app-common/src/main/java/com/ruoyi/common/utils/SecureSensitiveUtils.java

@@ -0,0 +1,117 @@
+package com.ruoyi.common.utils;
+
+import javax.crypto.Cipher;
+import javax.crypto.spec.GCMParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+import java.nio.charset.StandardCharsets;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.util.Base64;
+
+/**
+ * 敏感信息加密工具类(符合国密标准)
+ * - 身份证号:SM3/SHA-256 不可逆哈希
+ * - 银行账号:AES-256-GCM 可逆加密
+ */
+public class SecureSensitiveUtils {
+
+    // ==================== 加密(可逆) ====================
+    private static final int AES_KEY_LENGTH = 32; // 256-bit
+    private static final int GCM_IV_LENGTH = 12; // 12 bytes
+    private static final int GCM_TAG_LENGTH = 16; // 128-bit
+
+    /**
+     * 生成AES密钥(实际应从KMS获取)
+     */
+    public static String generateAesKey() throws NoSuchAlgorithmException {
+        String key = "B1a4z9jXeD7vGmKsPq2t5w8y/A6C0bRf";
+        return Base64.getEncoder().encodeToString(key.getBytes(StandardCharsets.UTF_8));
+//        KeyGenerator keyGen = KeyGenerator.getInstance("AES");
+//        keyGen.init(AES_KEY_LENGTH * 8); // 256-bit
+//        return Base64.getEncoder().encodeToString(keyGen.generateKey().getEncoded());
+    }
+
+    /**
+     * AES-GCM加密银行账号
+     */
+    public static String encrypt(String bankAccount, String base64Key) throws Exception {
+        // 解码密钥
+        byte[] key = Base64.getDecoder().decode(base64Key);
+        SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
+
+        // 生成IV
+        byte[] iv = new byte[GCM_IV_LENGTH];
+        SecureRandom random = new SecureRandom();
+        random.nextBytes(iv);
+
+        // 配置加密器
+        Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
+        GCMParameterSpec parameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, iv);
+        cipher.init(Cipher.ENCRYPT_MODE, keySpec, parameterSpec);
+
+        // 加密数据
+        byte[] ciphertext = cipher.doFinal(bankAccount.getBytes(StandardCharsets.UTF_8));
+
+        // 组合IV+密文
+        byte[] combined = new byte[iv.length + ciphertext.length];
+        System.arraycopy(iv, 0, combined, 0, iv.length);
+        System.arraycopy(ciphertext, 0, combined, iv.length, ciphertext.length);
+
+        return Base64.getEncoder().encodeToString(combined);
+    }
+
+    /**
+     * AES-GCM解密银行账号
+     */
+    public static String decrypt(String encrypted, String base64Key) throws Exception {
+        // 解码密钥
+        byte[] key = Base64.getDecoder().decode(base64Key);
+        SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
+
+        // 解码数据
+        byte[] combined = Base64.getDecoder().decode(encrypted);
+        byte[] iv = new byte[GCM_IV_LENGTH];
+        System.arraycopy(combined, 0, iv, 0, iv.length);
+
+        // 配置解密器
+        Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
+        GCMParameterSpec parameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, iv);
+        cipher.init(Cipher.DECRYPT_MODE, keySpec, parameterSpec);
+
+        // 解密数据
+        byte[] ciphertext = new byte[combined.length - GCM_IV_LENGTH];
+        System.arraycopy(combined, GCM_IV_LENGTH, ciphertext, 0, ciphertext.length);
+        return new String(cipher.doFinal(ciphertext), StandardCharsets.UTF_8);
+    }
+
+    // ==================== 辅助方法 ====================
+    /**
+     * 银行账号脱敏显示(如:6217****1234)
+     */
+    public static String maskBankAccount(String fullAccount) {
+        if (fullAccount == null || fullAccount.length() < 8) {
+            return fullAccount;
+        }
+        return fullAccount.substring(0, 4) + "****" + fullAccount.substring(fullAccount.length() - 4);
+    }
+
+    /**
+     * 身份证号脱敏显示(如:500108********71**)
+     */
+    public static String maskIdCard(String idCard) {
+        if (idCard == null || idCard.length() < 15) {
+            return idCard;
+        }
+        return idCard.substring(0, 6) + "********" + idCard.substring(14, 16) + "**";
+    }
+
+    /**
+     * 手机号脱敏显示(如:139****1324)
+     */
+    public static String maskPhone(String phone) {
+        if (phone == null || phone.length() < 11) {
+            return phone;
+        }
+        return phone.substring(0, 3) + "****" + phone.substring(phone.length() - 4);
+    }
+}

+ 3 - 3
sql/sql.sql

@@ -2,7 +2,7 @@
 CREATE TABLE person_info (
     id                  INT PRIMARY KEY AUTO_INCREMENT COMMENT 'ID',
     realname            VARCHAR(50) NOT NULL        COMMENT '姓名',
-    id_card             VARCHAR(18) UNIQUE          COMMENT '身份证号',
+    id_card             CHAR(64) UNIQUE          COMMENT '身份证号',
     ethnic              VARCHAR(30)                 COMMENT '民族',
     age                 TINYINT                     COMMENT '年龄',
     gender              TINYINT NOT NULL            COMMENT '性别: 1-男, 2-女, 0-未知',
@@ -37,7 +37,7 @@ CREATE TABLE household_info (
     id                    INT PRIMARY KEY AUTO_INCREMENT    COMMENT '户籍ID',
     household_code        VARCHAR(50) UNIQUE  NOT NULL      COMMENT '户籍编号',
     household_head          VARCHAR(50) NOT NULL            COMMENT '户主姓名',
-    household_head_id_card  VARCHAR(18)                     COMMENT '户主身份证号',
+    household_head_id_card  CHAR(64)                     COMMENT '户主身份证号',
     household_type          TINYINT     NOT NULL            COMMENT '户籍类型: 1-农业户口, 2-非农业户口',
     household_address       VARCHAR(200)                    COMMENT '户籍地址',
     belonging_area          VARCHAR(200)                    COMMENT '归属地区',
@@ -112,7 +112,7 @@ CREATE TABLE subsidy_projects (
     project_name VARCHAR(100) NOT NULL                  COMMENT '项目名称',
     subsidy_type VARCHAR(100) NOT NULL                  COMMENT '补贴类别',
     realname            VARCHAR(50) NOT NULL            COMMENT '姓名',
-    id_card        VARCHAR(18) UNIQUE                   COMMENT '身份证号',
+    id_card        CHAR(64) UNIQUE                   COMMENT '身份证号',
     subsidy_amount DECIMAL(20,2)                        COMMENT '实发金额(元)',
     bank_account_hash CHAR(64) NOT NULL                 COMMENT '银行账号SHA-256哈希(加密存储)',
     payment_date DATE NOT NULL                          COMMENT '兑现时间',