自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

50行代碼,搞定敏感數(shù)據(jù)讀寫!

開(kāi)發(fā) 前端
在實(shí)際的軟件系統(tǒng)開(kāi)發(fā)過(guò)程中,由于業(yè)務(wù)的需求,在代碼層面實(shí)現(xiàn)數(shù)據(jù)的脫敏還是遠(yuǎn)遠(yuǎn)不夠的,往往還需要在數(shù)據(jù)庫(kù)層面針對(duì)某些關(guān)鍵性的敏感信息,那在實(shí)際的研發(fā)過(guò)程中,我們?nèi)绾螌?shí)踐呢?

一、介紹

在實(shí)際的軟件系統(tǒng)開(kāi)發(fā)過(guò)程中,由于業(yè)務(wù)的需求,在代碼層面實(shí)現(xiàn)數(shù)據(jù)的脫敏還是遠(yuǎn)遠(yuǎn)不夠的,往往還需要在數(shù)據(jù)庫(kù)層面針對(duì)某些關(guān)鍵性的敏感信息,例如:身份證號(hào)、銀行卡號(hào)、手機(jī)號(hào)、工資等信息進(jìn)行加密存儲(chǔ),實(shí)現(xiàn)真正意義的數(shù)據(jù)混淆脫敏,以滿足信息安全的需要。

那在實(shí)際的研發(fā)過(guò)程中,我們?nèi)绾螌?shí)踐呢?

二、方案實(shí)踐

在此,提供三套方案以供大家選擇。

  • 通過(guò) SQL 函數(shù)實(shí)現(xiàn)加解密
  • 對(duì) SQL 進(jìn)行解析攔截,實(shí)現(xiàn)數(shù)據(jù)加解密
  • 自定義一套脫敏工具

1. 通過(guò) SQL 函數(shù)實(shí)現(xiàn)加解密

最簡(jiǎn)單的方法,莫過(guò)于直接在數(shù)據(jù)庫(kù)層面操作,通過(guò)函數(shù)對(duì)某個(gè)字段進(jìn)行加、解密,例如如下這個(gè)案例!

  1. -- 對(duì)“你好,世界”進(jìn)行加密 
  2. select   HEX(AES_ENCRYPT('你好,世界','ABC123456')); 
  3.  
  4. -- 解密,輸出:你好,世界 
  5. select  AES_DECRYPT(UNHEX('A174E3C13FE16AA0FD071A4BBD7CD7C5'),'ABC123456'); 

采用MySQL內(nèi)置的AES協(xié)議加、解密函數(shù),密鑰是ABC123456,可以很輕松的對(duì)某個(gè)字段實(shí)現(xiàn)加、解密。

如果是很小的需求,需要加密的數(shù)據(jù)就是指定的信息,此方法可行。

但是當(dāng)需要加密的表字段非常多的時(shí)候,這個(gè)使用起來(lái)就比較雞肋了,例如我想更改加密算法或者不同的部署環(huán)境配置不同的密鑰,這個(gè)時(shí)候就不得不把所有的代碼進(jìn)行更改一遍。

2. 對(duì) SQL 進(jìn)行解析攔截,實(shí)現(xiàn)數(shù)據(jù)加解密

通過(guò)上面的方案,我們發(fā)現(xiàn)最大的痛點(diǎn)就是加密算法和密鑰都寫死在SQL上了,因此我們可以將這塊的服務(wù)從抽出來(lái),在JDBC層面,當(dāng)sql執(zhí)行的時(shí)候,對(duì)其進(jìn)行攔截處理。

Apache ShardingSphere 框架下的數(shù)據(jù)脫敏模塊,它就可以幫助我們實(shí)現(xiàn)這一需求,如果你是SpringBoot項(xiàng)目,可以實(shí)現(xiàn)無(wú)縫集成,對(duì)原系統(tǒng)的改造會(huì)非常少。

下面以用戶表為例,我們來(lái)看看采用ShardingSphere如何實(shí)現(xiàn)!

(1) 創(chuàng)建用戶表

  1. CREATE TABLE user ( 
  2.   id bigint(20) NOT NULL COMMENT '用戶ID', 
  3.   email varchar(255)  NOT NULL DEFAULT '' COMMENT '郵件', 
  4.   nick_name varchar(255)  DEFAULT NULL COMMENT '昵稱', 
  5.   pass_word varchar(255)  NOT NULL DEFAULT '' COMMENT '二次密碼', 
  6.   reg_time varchar(255)  NOT NULL DEFAULT '' COMMENT '注冊(cè)時(shí)間', 
  7.   user_name varchar(255)  NOT NULL DEFAULT '' COMMENT '用戶名', 
  8.   salary varchar(255) DEFAULT NULL COMMENT '基本工資', 
  9.   PRIMARY KEY (id) USING BTREE 
  10. ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci

(2) 創(chuàng)建 springboot 項(xiàng)目并添加依賴包

  1. <dependencies> 
  2.     <!--spring boot核心--> 
  3.     <dependency> 
  4.         <groupId>org.springframework.boot</groupId> 
  5.         <artifactId>spring-boot-starter</artifactId> 
  6.     </dependency> 
  7.     <!--spring boot 測(cè)試--> 
  8.     <dependency> 
  9.         <groupId>org.springframework.boot</groupId> 
  10.         <artifactId>spring-boot-starter-test</artifactId> 
  11.         <scope>test</scope> 
  12.     </dependency> 
  13.     <!--springmvc web--> 
  14.     <dependency> 
  15.         <groupId>org.springframework.boot</groupId> 
  16.         <artifactId>spring-boot-starter-web</artifactId> 
  17.     </dependency> 
  18.     <!--mysql 數(shù)據(jù)源--> 
  19.     <dependency> 
  20.         <groupId>mysql</groupId> 
  21.         <artifactId>mysql-connector-java</artifactId> 
  22.     </dependency> 
  23.     <!--mybatis 支持--> 
  24.     <dependency> 
  25.         <groupId>org.mybatis.spring.boot</groupId> 
  26.         <artifactId>mybatis-spring-boot-starter</artifactId> 
  27.         <version>2.0.0</version> 
  28.     </dependency>  
  29.     <!--shardingsphere數(shù)據(jù)分片、脫敏工具--> 
  30.     <dependency> 
  31.         <groupId>org.apache.shardingsphere</groupId> 
  32.         <artifactId>sharding-jdbc-spring-boot-starter</artifactId> 
  33.         <version>4.1.0</version> 
  34.     </dependency> 
  35.     <dependency> 
  36.         <groupId>org.apache.shardingsphere</groupId> 
  37.         <artifactId>sharding-jdbc-spring-namespace</artifactId> 
  38.         <version>4.1.0</version> 
  39.     </dependency> 
  40. </dependencies> 

(3) 添加脫敏配置

在application.properties文件中,添加shardingsphere相關(guān)配置,即可實(shí)現(xiàn)針對(duì)某個(gè)表進(jìn)行脫敏。

  1. server.port=8080 
  2.  
  3. loglogging.path=log 
  4.  
  5. #shardingsphere數(shù)據(jù)源集成 
  6. spring.shardingsphere.datasource.name=ds 
  7. spring.shardingsphere.datasource.ds.type=com.zaxxer.hikari.HikariDataSource 
  8. spring.shardingsphere.datasource.ds.driver-class-name=com.mysql.cj.jdbc.Driver 
  9. spring.shardingsphere.datasource.ds.jdbc-url=jdbc:mysql://127.0.0.1:3306/test 
  10. spring.shardingsphere.datasource.ds.username=xxxx 
  11. spring.shardingsphere.datasource.ds.password=xxxx 
  12.  
  13. #加密方式、密鑰配置 
  14. spring.shardingsphere.encrypt.encryptors.encryptor_aes.type=aes 
  15. spring.shardingsphere.encrypt.encryptors.encryptor_aes.props.aes.key.value=hkiqAXU6Ur5fixGHaO4Lb2V2ggausYwW 
  16. #plainColumn表示明文列,cipherColumn表示脫敏列 
  17. springspring.shardingsphere.encrypt.tables.user.columns.salary.plainColumn
  18. spring.shardingsphere.encrypt.tables.user.columns.salary.cipherColumn=salary 
  19. #springspring.shardingsphere.encrypt.tables.user.columns.pass_word.assistedQueryColumn
  20. spring.shardingsphere.encrypt.tables.user.columns.salary.encryptor=encryptor_aes 
  21.  
  22. #sql打印 
  23. spring.shardingsphere.props.sql.show=true 
  24. spring.shardingsphere.props.query.with.cipher.column=true 
  25.  
  26.  
  27. #基于xml方法的配置 
  28. mybatis.mapper-locations=classpath:mapper/*.xml 

其中下面的配置信息是關(guān)鍵的一部,spring.shardingsphere.encrypt.tables是指要脫敏的表,user是表名,salary表示user表中的真實(shí)列,其中plainColumn指的是明文列,cipherColumn指的是脫敏列,如果是新工程,只需要配置脫敏列即可!

  1. springspring.shardingsphere.encrypt.tables.user.columns.salary.plainColumn
  2. spring.shardingsphere.encrypt.tables.user.columns.salary.cipherColumn=salary 
  3. #springspring.shardingsphere.encrypt.tables.user.columns.pass_word.assistedQueryColumn
  4. spring.shardingsphere.encrypt.tables.user.columns.salary.encryptor=encryptor_aes 

(4) 編寫數(shù)據(jù)持久層

  1. <mapper namespace="com.example.shardingsphere.mapper.UserMapperXml" > 
  2.  
  3.     <resultMap id="BaseResultMap" type="com.example.shardingsphere.entity.UserEntity" > 
  4.         <id column="id" property="id" jdbcType="BIGINT" /> 
  5.         <result column="email" property="email" jdbcType="VARCHAR" /> 
  6.         <result column="nick_name" property="nickName" jdbcType="VARCHAR" /> 
  7.         <result column="pass_word" property="passWord" jdbcType="VARCHAR" /> 
  8.         <result column="reg_time" property="regTime" jdbcType="VARCHAR" /> 
  9.         <result column="user_name" property="userName" jdbcType="VARCHAR" /> 
  10.         <result column="salary" property="salary" jdbcType="VARCHAR" /> 
  11.     </resultMap> 
  12.  
  13.     <select id="findAll" resultMap="BaseResultMap"> 
  14.         SELECT * FROM user 
  15.     </select> 
  16.      
  17.     <insert id="insert" parameterType="com.example.shardingsphere.entity.UserEntity"> 
  18.         INSERT INTO user(id,email,nick_name,pass_word,reg_time,user_name, salary) 
  19.         VALUES(#{id},#{email},#{nickName},#{passWord},#{regTime},#{userName}, #{salary}) 
  20.     </insert> 
  21. </mapper> 
  1. public interface UserMapperXml { 
  2.  
  3.  
  4.     /** 
  5.      * 查詢所有的信息 
  6.      * @return 
  7.      */ 
  8.     List<UserEntity> findAll(); 
  9.  
  10.     /** 
  11.      * 新增數(shù)據(jù) 
  12.      * @param user 
  13.      */ 
  14.     void insert(UserEntity user); 
  1. public class UserEntity { 
  2.  
  3.     private Long id; 
  4.  
  5.     private String email; 
  6.  
  7.     private String nickName; 
  8.  
  9.     private String passWord; 
  10.  
  11.     private String regTime; 
  12.  
  13.     private String userName; 
  14.  
  15.     private String salary; 
  16.  
  17.  //省略set、get... 
  18.  

(5) 最后我們來(lái)測(cè)試一下程序運(yùn)行情況

編寫啟用服務(wù)程序:

  1. @SpringBootApplication 
  2. @MapperScan("com.example.shardingsphere.mapper") 
  3. public class ShardingSphereApplication { 
  4.  
  5.     public static void main(String[] args) { 
  6.         SpringApplication.run(ShardingSphereApplication.class, args); 
  7.     } 

編寫單元測(cè)試:

  1. @RunWith(SpringJUnit4ClassRunner.class) 
  2. @SpringBootTest(classes = ShardingSphereApplication.class) 
  3. public class UserTest { 
  4.  
  5.     @Autowired 
  6.     private UserMapperXml userMapperXml; 
  7.  
  8.     @Test 
  9.     public void insert() throws Exception { 
  10.         UserEntity entity = new UserEntity(); 
  11.         entity.setId(3l); 
  12.         entity.setEmail("123@123.com"); 
  13.         entity.setNickName("阿三"); 
  14.         entity.setPassWord("123"); 
  15.         entity.setRegTime("2021-10-10 00:00:00"); 
  16.         entity.setUserName("張三"); 
  17.         entity.setSalary("2500"); 
  18.         userMapperXml.insert(entity); 
  19.     } 
  20.  
  21.     @Test 
  22.     public void query() throws Exception { 
  23.         List<UserEntity> dataList = userMapperXml.findAll(); 
  24.         System.out.println(JSON.toJSONString(dataList)); 
  25.     } 

插入數(shù)據(jù)后,如下圖,數(shù)據(jù)庫(kù)存儲(chǔ)的數(shù)據(jù)已被加密!

我們繼續(xù)來(lái)看看,運(yùn)行查詢服務(wù),結(jié)果如下圖,數(shù)據(jù)被成功解密!

采用配置方式,最大的好處就是直接通過(guò)配置脫敏列就可以完成對(duì)某些數(shù)據(jù)表字段的脫敏,非常方便。

3. 自定義一套脫敏工具

當(dāng)然,有的同學(xué)可能會(huì)覺(jué)得shardingsphere配置雖然簡(jiǎn)單,但是還是不放心,里面的很多規(guī)則自己無(wú)法掌控,想自己開(kāi)發(fā)一套數(shù)據(jù)庫(kù)的脫敏工具。

方案也是有的,例如如下這套實(shí)踐方案,以Mybatis為例:

  • 首先編寫一套加解密的算法工具類
  • 通過(guò)Mybatis的typeHandler插件,實(shí)現(xiàn)特定字段的加解密

實(shí)踐過(guò)程如下:

(1) 加解密工具類

  1. public class AESCryptoUtil { 
  2.  
  3.     private static final Logger log = LoggerFactory.getLogger(AESCryptoUtil.class); 
  4.  
  5.     private static final String DEFAULT_ENCODING = "UTF-8"
  6.     private static final String AES = "AES"
  7.  
  8.  
  9.     /** 
  10.      * 加密 
  11.      * 
  12.      * @param content 需要加密內(nèi)容 
  13.      * @param key     任意字符串 
  14.      * @return 
  15.      * @throws Exception 
  16.      */ 
  17.     public static String encryptByRandomKey(String content, String key) { 
  18.         try { 
  19.             //構(gòu)造密鑰生成器,生成一個(gè)128位的隨機(jī)源,產(chǎn)生原始對(duì)稱密鑰 
  20.             KeyGenerator keygen = KeyGenerator.getInstance(AES); 
  21.             SecureRandom random = SecureRandom.getInstance("SHA1PRNG"); 
  22.             random.setSeed(key.getBytes()); 
  23.             keygen.init(128, random); 
  24.             byte[] raw = keygen.generateKey().getEncoded(); 
  25.             SecretKey secretKey = new SecretKeySpec(raw, AES); 
  26.             Cipher cipher = Cipher.getInstance(AES); 
  27.             cipher.init(Cipher.ENCRYPT_MODE, secretKey); 
  28.             byte[] encrypted = cipher.doFinal(content.getBytes("utf-8")); 
  29.             return Base64.getEncoder().encodeToString(encrypted); 
  30.         } catch (Exception e) { 
  31.             log.warn("AES加密失敗,參數(shù):{},錯(cuò)誤信息:{}", content, e); 
  32.             return ""; 
  33.         } 
  34.     } 
  35.  
  36.     public static String decryptByRandomKey(String content, String key) { 
  37.         try { 
  38.             //構(gòu)造密鑰生成器,生成一個(gè)128位的隨機(jī)源,產(chǎn)生原始對(duì)稱密鑰 
  39.             KeyGenerator generator = KeyGenerator.getInstance(AES); 
  40.             SecureRandom random = SecureRandom.getInstance("SHA1PRNG"); 
  41.             random.setSeed(key.getBytes()); 
  42.             generator.init(128, random); 
  43.             SecretKey secretKey = new SecretKeySpec(generator.generateKey().getEncoded(), AES); 
  44.             Cipher cipher = Cipher.getInstance(AES); 
  45.             cipher.init(Cipher.DECRYPT_MODE, secretKey); 
  46.             byte[] encrypted = Base64.getDecoder().decode(content); 
  47.             byte[] original = cipher.doFinal(encrypted); 
  48.             return new String(original, DEFAULT_ENCODING); 
  49.         } catch (Exception e) { 
  50.             log.warn("AES解密失敗,參數(shù):{},錯(cuò)誤信息:{}", content, e); 
  51.             return ""; 
  52.         } 
  53.     } 
  54.  
  55.     public static void main(String[] args) { 
  56.         String encryptResult = encryptByRandomKey("Hello World", "123456"); 
  57.         System.out.println(encryptResult); 
  58.         String decryptResult = decryptByRandomKey(encryptResult, "123456"); 
  59.         System.out.println(decryptResult); 
  60.     } 

2.3.2、針對(duì) salary 字段進(jìn)行單獨(dú)解析

  1. <mapper namespace="com.example.shardingsphere.mapper.UserMapperXml" > 
  2.  
  3.     <resultMap id="BaseResultMap" type="com.example.shardingsphere.entity.UserEntity" > 
  4.         <id column="id" property="id" jdbcType="BIGINT" /> 
  5.         <result column="email" property="email" jdbcType="VARCHAR" /> 
  6.         <result column="nick_name" property="nickName" jdbcType="VARCHAR" /> 
  7.         <result column="pass_word" property="passWord" jdbcType="VARCHAR" /> 
  8.         <result column="reg_time" property="regTime" jdbcType="VARCHAR" /> 
  9.         <result column="user_name" property="userName" jdbcType="VARCHAR" /> 
  10.         <result column="salary" property="salary" jdbcType="VARCHAR" 
  11.                 typeHandler="com.example.shardingsphere.handle.EncryptDataRuleTypeHandler"/> 
  12.     </resultMap> 
  13.  
  14.     <select id="findAll" resultMap="BaseResultMap"> 
  15.         select * from user 
  16.     </select> 
  17.      
  18.     <insert id="insert" parameterType="com.example.shardingsphere.entity.UserEntity"> 
  19.         INSERT INTO user(id,email,nick_name,pass_word,reg_time,user_name, salary) 
  20.         VALUES( 
  21.         #{id}, 
  22.         #{email}, 
  23.         #{nickName}, 
  24.         #{passWord}, 
  25.         #{regTime}, 
  26.         #{userName}, 
  27.         #{salary,jdbcType=INTEGER,typeHandler=com.example.shardingsphere.handle.EncryptDataRuleTypeHandler}) 
  28.     </insert> 
  29. </mapper> 

EncryptDataRuleTypeHandler解析器,內(nèi)容如下:

  1. public class EncryptDataRuleTypeHandler implements TypeHandler<String> { 
  2.  
  3.     private static final String EMPTY = ""
  4.  
  5.     /** 
  6.      * 寫入數(shù)據(jù) 
  7.      * @param preparedStatement 
  8.      * @param i 
  9.      * @param data 
  10.      * @param jdbcType 
  11.      * @throws SQLException 
  12.      */ 
  13.     @Override 
  14.     public void setParameter(PreparedStatement preparedStatement, int i, String data, JdbcType jdbcType) throws SQLException { 
  15.         if (StringUtils.isEmpty(data)) { 
  16.             preparedStatement.setString(i, EMPTY); 
  17.         } else { 
  18.             preparedStatement.setString(i, AESCryptoUtil.encryptByRandomKey(data, "123456")); 
  19.         } 
  20.     } 
  21.  
  22.     /** 
  23.      * 讀取數(shù)據(jù) 
  24.      * @param resultSet 
  25.      * @param columnName 
  26.      * @return 
  27.      * @throws SQLException 
  28.      */ 
  29.     @Override 
  30.     public String getResult(ResultSet resultSet, String columnName) throws SQLException { 
  31.         return decrypt(resultSet.getString(columnName)); 
  32.     } 
  33.  
  34.     /** 
  35.      * 讀取數(shù)據(jù) 
  36.      * @param resultSet 
  37.      * @param columnIndex 
  38.      * @return 
  39.      * @throws SQLException 
  40.      */ 
  41.     @Override 
  42.     public String getResult(ResultSet resultSet, int columnIndex) throws SQLException { 
  43.         return decrypt(resultSet.getString(columnIndex)); 
  44.     } 
  45.  
  46.     /** 
  47.      * 讀取數(shù)據(jù) 
  48.      * @param callableStatement 
  49.      * @param columnIndex 
  50.      * @return 
  51.      * @throws SQLException 
  52.      */ 
  53.     @Override 
  54.     public String getResult(CallableStatement callableStatement, int columnIndex) throws SQLException { 
  55.         return decrypt(callableStatement.getString(columnIndex)); 
  56.     } 
  57.  
  58.     /** 
  59.      * 對(duì)數(shù)據(jù)進(jìn)行解密 
  60.      * @param data 
  61.      * @return 
  62.      */ 
  63.     private String decrypt(String data) { 
  64.         return AESCryptoUtil.decryptByRandomKey(data, "123456"); 
  65.     } 

(3) 單元測(cè)試

再次運(yùn)行單元測(cè)試,程序讀寫正常!

 

通過(guò)如下的方式,也可以實(shí)現(xiàn)對(duì)數(shù)據(jù)表中某個(gè)特定字段進(jìn)行數(shù)據(jù)脫敏處理!

三、小結(jié)

因業(yè)務(wù)的需求,當(dāng)需要對(duì)某些數(shù)據(jù)表字段進(jìn)行脫敏處理的時(shí)候,有個(gè)細(xì)節(jié)很容易遺漏,那就是字典類型,例如salary字段,根據(jù)常規(guī),很容易想到使用數(shù)字類型,但是卻不是,要知道加密之后的數(shù)據(jù)都是一串亂碼,數(shù)字類型肯定是無(wú)法存儲(chǔ)字符串的,因此在定義的時(shí)候,這個(gè)要留心一下。

其次,很多同學(xué)可能會(huì)覺(jué)得,這個(gè)也不能防范比人竊取數(shù)據(jù)啊!

如果加密使用的密鑰和數(shù)據(jù)都在一個(gè)項(xiàng)目里面,答案是肯定的,你可以隨便解析任何人的數(shù)據(jù)。因此在實(shí)際的處理上,這個(gè)更多的是在流程上做變化。例如如下方式:

  • 首先,加密采用的密鑰會(huì)在另外一個(gè)單獨(dú)的服務(wù)來(lái)存儲(chǔ)管理,保證密鑰不輕易泄露出去,最重要的是加密的數(shù)據(jù)不輕易被別人解密。
  • 其次,例如某些人想要訪問(wèn)誰(shuí)的工資條數(shù)據(jù),那么就需要做二次密碼確認(rèn),也就是輸入自己的密碼才能獲取,可以進(jìn)一步防止研發(fā)人員隨意通過(guò)接口方式讀取數(shù)據(jù)。
  • 最后就是,杜絕代碼留漏洞。

以上三套方案,都可以幫助大家實(shí)現(xiàn)數(shù)據(jù)庫(kù)字段數(shù)據(jù)的脫敏,希望能幫助到大家,謝謝欣賞!

 

 

責(zé)任編輯:趙寧寧 來(lái)源: Java極客技術(shù)
相關(guān)推薦

2010-09-25 08:55:29

2023-10-23 10:39:05

2020-10-25 09:04:46

數(shù)據(jù)加密數(shù)據(jù)泄露攻擊

2023-06-27 07:26:36

汽車之家敏感數(shù)據(jù)治理

2023-10-30 15:35:05

數(shù)據(jù)安全數(shù)據(jù)驅(qū)動(dòng)

2020-04-16 08:00:00

Ansible Vau敏感數(shù)據(jù)加密

2021-09-16 10:11:15

Dataphin 數(shù)據(jù)保護(hù)

2024-03-05 09:40:35

2011-08-01 14:36:06

加密RSA

2012-04-12 14:45:12

賽門鐵克云南電網(wǎng)

2024-01-01 15:53:25

2020-12-20 17:30:17

數(shù)據(jù)匿名化敏感數(shù)據(jù)數(shù)據(jù)庫(kù)

2025-01-21 14:48:39

2012-07-03 11:35:02

數(shù)據(jù)安全

2013-09-12 13:23:06

2021-09-18 10:06:06

數(shù)據(jù)安全隱私計(jì)算大數(shù)據(jù)

2018-09-14 14:48:01

2023-06-06 08:51:06

2020-07-06 09:21:52

云平臺(tái)云安全公共云

2023-07-21 12:48:37

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)