目录
1,实验环境
Windows10
sonarqube-6.7.4
sonar-scanner-2.8
2,前言(环境配置)
具体安装配置过程教程比较多,这里就不再详细介绍了。
1,在官网下载sonarqube和sonar-scanner后,解压、修改配置(主要是连接数据库);
2,在sonarqube的bin目录下运行StartSonar.bat后,可以看到
证明启动成功了;
3,浏览器地址栏输入localhost:9000(默认端口号)就能进入sonarqube登录界面,账号密码默认都是admin;
4,执行代码扫描的功能主要通过sonar-scanner进行,通常的扫描方法是在sonar-scanner/conf下,编辑sonar-scanner.properties文件,配置数据库及编码信息:
#Configure here general information about the environment, such as SonarQube server connection details for example #No information about specific project should appear here #----- Default SonarQube server #sonar.host.url=http://localhost:9000 sonar.host.url=http://127.0.0.1:9000 #----- Default source code encoding #sonar.sourceEncoding=UTF-8 sonar.sourceEncoding=UTF-8 sonar.jdbc.url=jdbc:mysql://127.0.0.1:3306/sonar?useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true&useConfigs=maxPerformance sonar.jdbc.username=填写数据库sonar对应的用户名 sonar.jdbc.password=填写数据库sonar对应的用户名密码 sonar.language=java #----- sonar登录账户 sonar.login=admin sonar.password=admin http.authentication.preemptive=true http.socket.timeout = 60000
编辑wrapper.conf文件,添加jdk版本信息
在待扫描项目的主目录下新建sonar-project.properties文件,编写项目相关的配置:
# must be unique in a given SonarQube instance sonar.projectKey=news # this is the name displayed in the SonarQube UI sonar.projectName=news sonar.projectVersion=1.0 # Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows. # Since SonarQube 4.2, this property is optional if sonar.modules is set. # If not set, SonarQube starts looking for source code from the directory containing # the sonar-project.properties file. # 源代码目录 sonar.sources=src # 编译生成的class文件存放目录(有的项目是存放在target目录中) sonar.java.binaries=build/classes # Encoding of the source code. Default is default system encoding #sonar.sourceEncoding=UTF-8
有同学在sonarqube中安装sonarjava插件版本过高,所以配置文件中不添加sonar.java.binaries 就会报错,如果想采用静态代码检测(不经过编译,没有生成的classes文件),可以这样编写属性
,解决方法来自这篇文章的【评论】@pengyuan_D【解决新版sonar-java插件需要配置sonar.java.binaries参数的问题】
在待扫描项目的主目录中打开命令行,运行sonar-scanner就可以看到执行流程了
5,如果出现
是因为sonar-scanner支持jdk11版本,和项目所支持的Java1.8版本冲突,可以更换sonar-scanner为2.8版本解决,具体可以参考这里@<予安>【SonarQube执行代码分析时,报错ERROR: Unable to create symbol table for : /**/*.java java.lang.IllegalArgumentException: Unsupported class file major version 55】,亲测有效
PS D:\news> sonar-scanner C:\Program Files\sonar-scanner-2.8\bin\.. INFO: Scanner configuration file: C:\Program Files\sonar-scanner-2.8\bin\..\conf\sonar-scanner.properties INFO: Project root configuration file: D:\news\sonar-project.properties INFO: SonarQube Scanner 2.8 INFO: Java 1.8.0_271 Oracle Corporation (64-bit) INFO: Windows 10 10.0 amd64 INFO: User cache: C:\Users\许逍遥\.sonar\cache INFO: Publish mode INFO: Load global settings INFO: Load global settings (done) | time=70ms INFO: Server id: AXdNgQHKmLEJg0fPaxh9 INFO: User cache: C:\Users\许逍遥\.sonar\cache INFO: Load plugins index INFO: Load plugins index (done) | time=55ms INFO: SonarQube server 6.7.4 INFO: Default locale: "zh_CN", source code encoding: "GBK" (analysis is platform dependent) INFO: Process project properties INFO: Load project repositories INFO: Load project repositories (done) | time=100ms INFO: Load quality profiles INFO: Load quality profiles (done) | time=30ms INFO: Load active rules INFO: Load active rules (done) | time=559ms INFO: Load metrics repository INFO: Load metrics repository (done) | time=32ms WARN: SCM provider autodetection failed. No SCM provider claims to support this project. Please use sonar.scm.provider to define SCM of your project. INFO: Project key: news INFO: ------------- Scan news INFO: Load server rules INFO: Load server rules (done) | time=54ms INFO: Base dir: D:\news INFO: Working dir: D:\news\.sonar INFO: Source paths: src INFO: Source encoding: GBK, default locale: zh_CN INFO: Index files INFO: 32 files indexed INFO: Quality profile for java: Sonar way INFO: Quality profile for xml: Sonar way INFO: Sensor JavaSquidSensor [java] INFO: Configured Java source version (sonar.java.source): none INFO: JavaClasspath initialization WARN: Bytecode of dependencies was not provided for analysis of source files, you might end up with less precise results. Bytecode can be provided using sonar.java.libraries property INFO: JavaClasspath initialization (done) | time=25ms INFO: JavaTestClasspath initialization INFO: JavaTestClasspath initialization (done) | time=2ms INFO: Java Main Files AST scan INFO: 22 source files to be analyzed INFO: 22/22 source files have been analyzed INFO: Java Main Files AST scan (done) | time=2406ms INFO: Java Test Files AST scan INFO: 0 source files to be analyzed INFO: Java Test Files AST scan (done) | time=1ms INFO: 0/0 source files have been analyzed INFO: Sensor JavaSquidSensor [java] (done) | time=3189ms INFO: Sensor SurefireSensor [java] INFO: parsing [D:\news\target\surefire-reports] INFO: Sensor SurefireSensor [java] (done) | time=5ms INFO: Sensor JaCoCoSensor [java] INFO: Sensor JaCoCoSensor [java] (done) | time=2ms INFO: Sensor SonarJavaXmlFileSensor [java] INFO: 7 source files to be analyzed INFO: Sensor SonarJavaXmlFileSensor [java] (done) | time=189ms INFO: 7/7 source files have been analyzed INFO: Sensor XML Sensor [xml] INFO: Sensor XML Sensor [xml] (done) | time=520ms INFO: Sensor Analyzer for "php.ini" files [php] INFO: Sensor Analyzer for "php.ini" files [php] (done) | time=11ms INFO: Sensor Zero Coverage Sensor INFO: Sensor Zero Coverage Sensor (done) | time=97ms INFO: Sensor CPD Block Indexer INFO: Sensor CPD Block Indexer (done) | time=100ms INFO: No SCM system was detected. You can use the 'sonar.scm.provider' property to explicitly specify it. INFO: 8 files had no CPD blocks INFO: Calculating CPD for 14 files INFO: CPD calculation finished INFO: Analysis report generated in 319ms, dir size=123 KB INFO: Analysis reports compressed in 129ms, zip size=71 KB INFO: Analysis report uploaded in 36ms INFO: ANALYSIS SUCCESSFUL, you can browse http://localhost:9000/dashboard/index/news INFO: Note that you will be able to access the updated dashboard once the server has processed the submitted analysis report INFO: More about the report processing at http://localhost:9000/api/ce/task?id=AXdRaLa6jIZ5qmW-FA5u INFO: Task total time: 8.065 s INFO: ------------------------------------------------------------------------ INFO: EXECUTION SUCCESS INFO: ------------------------------------------------------------------------ INFO: Total time: 11.348s INFO: Final Memory: 52M/261M INFO: ------------------------------------------------------------------------
至此,已经可以通过常规方法使用sonarqube扫描一个Java项目了,下面正片开始ε=ε=ε=(~ ̄▽ ̄)~
以下步骤均在sonarqube开启的前提下进行
3,通过Java执行shell命令扫描项目
3.1 主要思路
用户输入项目路径projectPath、项目名称/标识符projectName(这里默认将项目名称作为key);
在项目目录下创建sonarqube扫描所需的配置文件sonar-project.properties,并填入配置信息;
通过Runtime.getRuntime().exec执行命令行程序,并通过Process对象的waitFor函数了解进程的运行结果;
3.2 参考代码
import java.io.*;
import java.net.HttpURLConnection;
import java.util.ArrayList;
import java.util.Scanner;
import com.alibaba.fastjson.*;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.Objects;
public class Main {
public static Scanner input = new Scanner(System.in);
public static void main(String[] args) {
// 输入配置信息
System.out.println("输入待扫描项目地址:");
String projectPath = input.nextLine();
String fileName = "sonar-project.properties";// 配置文件名称
System.out.println("输入项目名称:");
String projectName = input.nextLine();
String projectVersion = "1.0";
String sources = "src";
String binaries = "./";
// 创建配置文件
createFile(projectPath.concat("/"), fileName, projectName, projectVersion, sources, binaries);
// 运行命令行
runShell(projectPath);
}
/**
* 创建配置文件
* @param projectPath
* @param fileName
* @param projectName
* @param projectVersion
* @param sources
* @param binaries
*/
public static void createFile(String projectPath, String fileName,String projectName,
String projectVersion, String sources, String binaries) {
// 创建配置文件
File file = new File(projectPath, fileName);
if(file.exists()) {
System.out.println("配置文件已存在,开始更新配置");
} else {
try {
file.createNewFile();
System.out.println("配置文件创建成功,开始更新配置");
} catch (IOException e) {
e.printStackTrace();
}
}
// 向文件中添加配置信息
FileWriter fw;
try {
fw = new FileWriter(projectPath + fileName);
BufferedWriter bw = new BufferedWriter(fw);
bw.write("sonar.projectKey=" + projectName + "\n");
bw.write("sonar.projectName=" + projectName + "\n");
bw.write("sonar.projectVersion=" + projectVersion + "\n");
bw.write("sonar.sources=" + sources + "\n");
bw.write("sonar.java.binaries=" + binaries + "\n");
bw.write("sonar.sourceEncoding=UTF-8\n");
bw.close();
} catch (IOException e) {
e.printStackTrace();
System.out.println(2);
}
}
/**
* 打开命令行,切换到对应目录,执行sonar-scanner指令
* @param projectPath
*/
public static void runShell (String projectPath) {
try {
long startTime = System.currentTimeMillis();
Process proc = Runtime.getRuntime().exec("cmd.exe /c cd " + projectPath + "&& sonar-scanner");
int processCode = proc.waitFor();
if(processCode == 0) {
System.out.println("扫描完成");
long endTime = System.currentTimeMillis();
// 获取扫描时间
long usedTime = (endTime - startTime) / 1000;
System.out.println("扫描用时" + usedTime + "s");
System.out.println("-----------------------------");
} else {
System.out.println("扫描失败");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
3.3 运行效果
4,通过sonarqube的webapi获取项目扫描结果
4.1 主要思路
向接口(http://localhost:9000/api/measures/component?component=项目的key&metricKeys=想要获得的指标)发送HTTP请求,获得返回的json字符串;
借助阿里爸爸的fastJSON
将json字符串转换为jsonObject对象,并通过不断解析,得到想要得到的字段(这里指bugs/code_smells/vulnerabilities的数目);
4.2 参考代码
/**
* 根据项目名称获取sonarqube扫描结果(bugs、codeSmells、vulnerabilities)
* @param projectName
*/
public static void getJsonData(String projectName) {
String param1 = "component=" + projectName + "&metricKeys=bugs";
String param2 = "component=" + projectName + "&metricKeys=code_smells";
String param3 = "component=" + projectName + "&metricKeys=vulnerabilities";
System.out.println("bugs:" + getSonarMeasures(param1));
System.out.println("codeSmells:" + getSonarMeasures(param2));
System.out.println("vulnerabilities:" + getSonarMeasures(param3));
}
/**
* 根据参数获得相应的指标
* @param param 向接口发送的参数(bugs、codeSmells、vulnerabilities)
* @return 各种参数的值
*/
public static int getSonarMeasures(String param) {
PrintWriter out = null;
InputStream is = null;
BufferedReader br = null;
StringBuilder sb = new StringBuilder();
int value = 0;
try {
String api = "http://localhost:9000/api/measures/component?";
URL url = new URL(api);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
// 发送参数
connection.setDoOutput(true);
out = new PrintWriter(connection.getOutputStream());
out.print(param);
out.flush();
// 接受结果
is = connection.getInputStream();
br = new BufferedReader(new InputStreamReader(is, "UTF-8"));
// 通过流读取结果
String line;
while ((line = br.readLine()) != null) {
sb.append(line);
}
// 解析json数据
String backJson = sb.toString(); // 获得json字符串
JSONObject jsonObject = JSONObject.parseObject(backJson); // 将字符串转换为JSONObject对象
JSONObject componentObj = jsonObject.getJSONObject("component"); // 获取component的JSONObject对象
JSONArray measuresAry = componentObj.getJSONArray("measures"); // 由于是数组形式,先获取measures的JSONArray对象
JSONObject measuresObj = measuresAry.getJSONObject(0); // 获取measures的JSONObject对象
value = measuresObj.getIntValue("value");
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if(is != null) is.close();
if(br != null) br.close();
if(out != null) out.close();
} catch (Exception e) {
e.printStackTrace();
}
}
return value;
}