一、引言
一个优秀的IT技术民工,需要始终学习先进的技术并将技术转换为生产力,目前AI领域编码辅助工具层出不穷,开发者有必要或者说必须要掌握相关工具的使用,以提高编码效率,降低编码错误。这次我通过一个简单的项目和大家分享一下腾讯云代码助手的使用。
二、开发环境介绍
我们使用目前应用非常广泛的开发语言java,使用IntelliJ IDEA作为我们的开发工具,目前腾讯云代码助手已经支持以插件模式安装到IntelliJ IDEA中,详细安装方式可以官方文档,我采用下载到本地磁盘的安装方式,安装后需要登录腾讯云后使用。选用java开发的原因主要还是个人习惯,对于java比较熟悉,不过也是第一次去开发这种数据库安全的扫描小工具,还是有一些挑战。
三、腾讯云AI代码助手使用实践
3.1、开发背景简要说明
由于某些原因,需要对公司内部所有数据库进行检查,不允许数据库(测试用)中存放任何敏感信息,包括身份证号,手机号,银行卡号,由于数据库众多,数据量巨大,如果进行人工核验非常耗时耗力,最重要的是难免会有所遗漏,所以决定开发一个简单的小工具来实现此功能,主要包括以下功能
1、多数据库连接,一次性完成所有数据库的扫描,以提升效率
2、自动获取一个数据库服务下的多个db,并进行数据扫描。
3、匹配数据格式是否是敏感信息
4、将识别到的敏感信息输出日志,以便后续反馈相关人员进行调整
3.2、coding
1、首先定义多个数据库的连接方式,java程序从yaml文件中读取所有数据库的连接信息。通过循环遍历方式逐一连接扫描
yaml文件格式,这种定义方式可以将多个数据库信息写在一起,一次扫描所有数据库服务器
代码语言:yaml复制databases:
- host: 192.168.1.2
port: 3306
username: root
password: MysqlPasswd
- host: 192.168.1.3
port: 3306
username: root
password: MysqlPasswd
定义类用于绑定yaml,我们将yaml文件的数据库信息映射成DatabaseInfo对象,方便我们后续的操作
代码语言:java复制package org.checkdb;
public class DatabaseInfo {
String databaseHost;
String databasePort;
String databaseUser;
String databasePassword;
// Getter and Setter methods
public String getHost() {
return databaseHost;
}
public void setHost(String databaseHost) {
this.databaseHost = databaseHost;
}
public String getPort() {
return databasePort;
}
public void setPort(String databasePort) {
this.databasePort = databasePort;
}
public String getUsername() {
return databaseUser;
}
public void setUsername(String databaseUser) {
this.databaseUser = databaseUser;
}
public String getPassword() {
return databasePassword;
}
public void setPassword(String databasePassword) {
this.databasePassword = databasePassword;
}
}
在mian中绑定yaml和类,在工具启动时映射dataeaseinfo对象中,这部分代码我们使用腾讯云代码助手帮我们生成一个简单的例子,我们简单修改完成。我们可以通过问答的方式获取相关例子
通过腾讯云代码助手给出的例子,简单修改后的代码,别忘了添加依赖
代码语言:java复制package org.checkdb.utils;
import org.checkdb.DatabaseInfo;
import org.yaml.snakeyaml.Yaml;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class DatabaseMapper {
private static final Yaml yaml = new Yaml();
public static List<DatabaseInfo> mapDatabasesFromYaml(String yamlFilePath) {
InputStream inputStream = DatabaseMapper.class.getClassLoader().getResourceAsStream(yamlFilePath);
Map<String, List<Map<String, String>>> databasesMap = yaml.load(inputStream);
List<DatabaseInfo> databaseInfos = new ArrayList<>();
for (Map<String, String> databaseMap : databasesMap.get("databases")) {
DatabaseInfo databaseInfo = new DatabaseInfo();
databaseInfo.setHost(databaseMap.get("host"));
databaseInfo.setPort(String.valueOf(databaseMap.get("port")));
databaseInfo.setUsername(databaseMap.get("username"));
databaseInfo.setPassword(databaseMap.get("password"));
databaseInfos.add(databaseInfo);
}
return databaseInfos;
}
}
在main方法中增加调用,这样我们就在mian方法中获取到了所有的数据库信息List<DatabaseInfo>
代码语言:java复制 public static void main(String[] args) {
List<DatabaseInfo> databaseInfos = DatabaseMapper.mapDatabasesFromYaml("databases.yaml");
}
}
2、编写连接数据库的方法
获取单个mysql中的所有database,然后我们把这个方法放到main中的,对databaseInfos进行循环,依次获取所有mysql数据库中的所有database。我们继续使用腾讯云代码助手帮助我们提供示例
编写获取所有数据库方法
代码语言:java复制 public static List<String> getAllDatabases(DatabaseInfo databaseInfo) {
List<String> databases = new ArrayList<>();
try {
Connection conn = DriverManager.getConnection("jdbc:mysql://" databaseInfo.getHost() ":" databaseInfo.getPort() "?useSSL=false&serverTimezone=UTC", databaseInfo.getUsername(), databaseInfo.getPassword());
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SHOW DATABASES");
while (rs.next()) {
databases.add(rs.getString(1));
}
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
return databases;
}
在main方法中增加调用,现在我们可以依次获取所有mysql数据库中的所有database
代码语言:java复制 public static void main(String[] args) {
List<DatabaseInfo> databaseInfos = DatabaseMapper.mapDatabasesFromYaml("databases.yaml");
for (DatabaseInfo databaseInfo : databaseInfos) {
List<String> databases = DatabaseConnector.getAllDatabases(databaseInfo);
}
}
获取单个database中的所有表,这次我们换一种新的方式使用腾讯云代码助手,通过编写代码注释,让腾讯代码助手帮我们生成代码
编写获取所有表的方法
代码语言:java复制 public static List<String> getAllTables(DatabaseInfo databaseInfo, String databaseName) {
List<String> tables = new ArrayList<>();
try {
Connection conn = DriverManager.getConnection("jdbc:mysql://" databaseInfo.getHost() ":" databaseInfo.getPort() "?useSSL=false&serverTimezone=UTC", databaseInfo.getUsername(), databaseInfo.getPassword());
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SHOW TABLES FROM " databaseName);
while (rs.next()) {
tables.add(rs.getString(1));
}
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
return tables;
}
在main方法中增加调用,现在我们可以依次获取所有mysql数据库中的所有database的所有table
代码语言:java复制 public static void main(String[] args) {
List<DatabaseInfo> databaseInfos = DatabaseMapper.mapDatabasesFromYaml("databases.yaml");
for (DatabaseInfo databaseInfo : databaseInfos) {
List<String> databases = DatabaseConnector.getAllDatabases(databaseInfo);
for (String database : databases) {
List<String> tables = DatabaseConnector.getAllTables(databaseInfo,database);
}
}
}
3、数据格式校验方法
现在我们已经有了所有database以及table,我们继续编写效验身份证格式,电话号格式和银行卡格式的方法,我们继续使用通过注释的方式生成代码
代码语言:java复制package org.checkdb.utils;
public class CheckRE {
// 判断数据格式是否是身份证号
public static boolean isIdCard(String idCard) {
return idCard.matches("^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$");
}
// 判断数据格式是否是手机号
public static boolean isMobileNO(String mobiles) {
return mobiles.matches("^((13[0-9])|(14[5,7])|(15[0-3,5-9])|(17[0,3,5-8])|(18[0-9])|166|198|199|(147))\d{8}$");
}
// 判断数据格式是否是银行卡号
public static boolean isBankCard(String bankCard) {
return bankCard.matches("^[1-9]\d{9,29}$");
}
}
注:这些判断方法肯定不是非常严格的验证,但是用于判断数据库中是否存在这些敏感类型的数据是足够了
4、扫描逻辑
最后我们来编写扫描逻辑,因为数据表中的数据非常多,我们不可全表扫描,所以我们获取每张表的前500条数据进行判断,如果存在敏感数据,我们记录到文件中,依然腾讯代码助手
代码语言:java复制package org.checkdb.utils;
import org.checkdb.DatabaseInfo;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.sql.*;
import java.util.ArrayList;
import java.util.Objects;
import static org.checkdb.utils.CheckRE.*;
public class ScanDatabase {
public static void ScanTable(DatabaseInfo databaseInfo,String databaseName,String tableName){
if (Objects.equals(databaseName, "mysql") || Objects.equals(databaseName, "sys") || Objects.equals(databaseName, "performance_schema") || Objects.equals(databaseName, "information_schema")) {
return;
}
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try {
// 连接数据库
conn = DriverManager.getConnection("jdbc:mysql://" databaseInfo.getHost() ":" databaseInfo.getPort() "/" databaseName "?useSSL=false&serverTimezone=UTC", databaseInfo.getUsername(), databaseInfo.getPassword());
// 获取表的所有字段信息
DatabaseMetaData metaData = conn.getMetaData();
ResultSet fieldsRS = metaData.getColumns(null, null, tableName, null);
// 存储字段名
ArrayList<String> fields = new ArrayList<>();
while (fieldsRS.next()) {
fields.add(fieldsRS.getString("COLUMN_NAME"));
}
// 执行SQL查询,获取前500条数据
String sql = "SELECT * FROM " tableName " LIMIT 500";
stmt = conn.createStatement();
rs = stmt.executeQuery(sql);
// 判断每个字段是否包含敏感信息并写入文件
BufferedWriter writer = new BufferedWriter(new FileWriter("sensitive_data.txt", true));
while (rs.next()) {
for (String field : fields) {
String value = rs.getString(field);
if (value == null) {
continue;
}
if (isIdCard(value)) {
writer.write("数据库IP: " databaseInfo.getHost() "数据库名称: " databaseName "表名称:" tableName "字段名: " field "存在敏感数据: " value "n");
break;
}
if (isMobileNO(value)) {
writer.write("数据库IP: " databaseInfo.getHost() "数据库名称: " databaseName "表名称:" tableName "字段名: " field "存在敏感数据: " value "n");
break;
}
if (isBankCard(value)) {
writer.write("数据库IP: " databaseInfo.getHost() "数据库名称: " databaseName "表名称:" tableName "字段名: " field "存在敏感数据: " value "n");
break;
}
}
}
writer.close();
} catch (SQLException | IOException e) {
e.printStackTrace();
} finally {
// 关闭资源
try {
if (rs != null) rs.close();
if (stmt != null) stmt.close();
if (conn != null) conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
在main方法中增加调用
代码语言:javascript复制public class Main {
public static void main(String[] args) {
List<DatabaseInfo> databaseInfos = DatabaseMapper.mapDatabasesFromYaml("databases.yaml");
for (DatabaseInfo databaseInfo : databaseInfos) {
List<String> databases = DatabaseConnector.getAllDatabases(databaseInfo);
for (String database : databases) {
List<String> tables = DatabaseConnector.getAllTables(databaseInfo,database);
for (String table : tables){
ScanDatabase.ScanTable(databaseInfo,database,table);
}
}
}
}
}
注:1、要排除Mysql自带库。2、要考虑查询值空指针
3.3、code review
现在我们代码逻辑和主要功能都已经实现,但是还是需要进行code review,以便检查出代码中存在的潜在问题,还好腾讯云代码助手提供了代码优化功能,我们使用一下看看效果,此处主要的两个功能是,代码优化和缺陷检查
代码优化,我们对scanDatabase进行优化。
该功能给出了一下优化建议,以及示例代码,我们可以参考和建议进行修改
1、使用try-with-resources语句自动关闭资源,避免潜在的资源泄露。
2、将敏感信息的检测逻辑封装成方法,提高代码的可读性和可维护性。
3、使用预编译语句(PreparedStatement)来执行SQL查询,提高代码的安全性和性能。
4、避免在循环中频繁地打开和关闭文件,可以考虑使用try-with-resources语句来管理文件资源。
缺陷检查,我们对scanDatabase进行缺陷检查。
该功能给出的建议如下
1、SQL注入风险:原代码中直接拼接SQL查询语句,存在SQL注入的风险。为了避免这个问题,应该使用PreparedStatement来代替Statement。 2、资源关闭异常:在finally块中关闭资源时,如果任何一个资源关闭失败,后续的资源关闭操作将不会被执行。应该分别捕获每个资源的关闭异常。 3、文件写入异常处理:在写入文件时,如果发生异常,可能会导致文件没有被正确关闭。应该使用try-with-resources语句来确保文件在异常发生时也能被正确关闭。 4、数据库连接字符串硬编码:数据库连接字符串中的useSSL=false&serverTimezone=UTC可能不适用于所有情况,应该允许通过参数传递。 5、文件写入路径硬编码:写入文件的路径被硬编码为sensitive_data.txt,这可能导致文件被覆盖或写入到不期望的位置。应该允许通过参数传递文件路径。
code review小结
通过使用代码优化、代码缺陷检查,可以帮助我们快速发现解决问题,但是给出的建议并非是一定要采纳的,需要我们根据情况具体分析,比如说存在sql注入风险这一块,所有sql均在代码内部,并不接收任何参数作为sql的一部分。
3.4、testing
创建了两个mysql数据库,写了一些假数据进行测试可以看到我们已经查询出存在敏感字段的内容
注:确实测试的是两个数据库服务器,只不过在一天服务器上部署的,只记录了服务器IP所以区分不不出来,手动滑稽。。。。
四、获得帮助与提升
通过使用腾讯云代码助手,可以极大的提交coding效率,逻辑简单的代码可以自动补全,通过问答方式可以快速获取想要的例子。本次开发通过问答的方式解决了一部分代码逻辑设计的问题(因为我没有思考如何设计,只是把问题抛给腾讯云代码助手。。。)可是说是小白式开发;还有很多的功能我实际用到了,但是文章中没有体现出来,比如出现报错时,腾讯云代码助手可以帮助定位问题,还有就是可以快速帮你生成代码注释,简直懒人神器
这个没啥说的等修复吧
五、建议
额不知道写这些好不好,不过确实还是存在一些问题
5.1、疑似bug
此处我疯狂在输入框按上下箭头,但是光标就是不移动,只能左右移动
5.2、无法生成复杂逻辑
此处我本意是要校验身份证号的行政区划,生日等是否是身份证格式的,不知道是不是我提问方式问题导致。。。
这个问题感觉需要好好说一下,代码补全和问答这一块,感觉不仅仅要有足够的知识宽度,还需要有一定的深度,累死身份证格式校验的功能其实网上有很多的实现,并不是偏门的技术实现,这个问题没能给出一个非常好非常高质量的回答确实是我万万没想到的,腾讯云代码助手在这个方面应该还有很大的进步空间
5.3、代码补全会出现不能正常运行的代码
这个问题出现很多次,但是都是我没有录制视频的时候。。。简单描述一下就是在代码进行补全时给出的方法是不存在的,或者给出的方法参数不正确等,感觉是不是训练时知识库存在错误知识导致的呢。
六、结语
通过使用腾讯云代码助手的问答、代码补全、代码优化、缺陷检查等功能,我们顺利完成了这个小工具的开发,只能说功能非常好用,但是腾讯代码助手好虽好,但千万不能过于依赖,开发人员吃饭的本钱就是coding,如果一味依赖工具反而是本末倒置,但是并不是说我们不能用啊,相反我非常支持使用腾讯云代码助手,只是使用方式上我倾向于将其做一个大辞典,当我们遇到不会的问题可以快速准确的查询,同时还可以把他当成一个快速补全代码的工具,当我们明确知道自己想要什么代码的时候,通过腾讯云代码助手进行补全,我们进行核对,依次来提升效率。最后祝愿腾讯云代码助手越来越棒