代码扫描平台SonarQube部署及配置jenkins集成

Sonar是一个用于代码质量管理的开源平台,用于管理源代码的质量,可以从七个维度检测代码质量,通过插件形式,可以支持包括java,C#,C/C++,PL/SQL,Cobol,JavaScrip,Groovy等等二十几种编程语言的代码质量管理与检测。

docker-compose安装SonarQube

docker-compose.yml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
version: '3'
services:
postgres:
image: docker.1ms.run/postgres:12
container_name: postgres
restart: always
privileged: true
volumes:
- ./postgres/postgresql:/var/lib/postgresql
- ./postgres/data:/var/lib/postgresql/data
- /etc/localtime:/etc/localtime:ro
ports:
- "5432:5432"
environment:
POSTGRES_USER: sonar
POSTGRES_PASSWORD: sonar
POSTGRES_DB: sonar
TZ: Asia/Shanghai

sonar:
image: docker.1ms.run/sonarqube:9.9.6-community
container_name: sonar
restart: always
privileged: true
depends_on:
- postgres
volumes:
- ./sonarqube/logs:/opt/sonarqube/logs
- ./sonarqube/conf:/opt/sonarqube/conf
- ./sonarqube/data:/opt/sonarqube/data
- ./sonarqube/extensions:/opt/sonarqube/extensions
ports:
- "19000:9000"
environment:
SONARQUBE_JDBC_USERNAME: sonar
SONARQUBE_JDBC_PASSWORD: sonar
SONARQUBE_JDBC_URL: "jdbc:postgresql://postgres:5432/sonar"

创建插件目录,上传插件到该目录下:

1
sonarqube/extensions/plugins

插件:

多分支:

sonarqube-community-branch-plugin-1.14.0.jar

下载链接:https://github.com/mc1arke/sonarqube-community-branch-plugin/releases/download/1.14.0/sonarqube-community-branch-plugin-1.14.0.jar

汉化:

sonar-l10n-zh-plugin-9.9.jar

下载链接:https://github.com/xuhuisheng/sonar-l10n-zh/releases

注:以上插件适配9.9版本的SonarQube,其他版本的需要找对应的版本插件。

配置权限:

1
chown 1000:1000 -R sonarqube/extensions/plugins

加载插件:

1
2
3
4
5
6
7
8
vim sonarqube/conf/sonar.properties

sonar.web.javaAdditionalOpts=-javaagent:/opt/sonarqube/extensions/plugins/sonarqube-community-branch-plugin-1.14.0.jar=web
sonar.ce.javaAdditionalOpts=-javaagent:/opt/sonarqube/extensions/plugins/sonarqube-community-branch-plugin-1.14.0.jar=ce

sonar.jdbc.username=sonar
sonar.jdbc.password=sonar
sonar.jdbc.url=jdbc:postgresql://postgres:5432/sonar

配置权限:

1
chown 1000:1000 -R sonarqube/conf

启动服务:

1
docker-compose up -d

启动完成后,通过10.168.2.236:19000访问,初始账号密码是admin/admin

jenkins集成SonarQube

jenkins安装SonarQube Scanner插件

插件市场的版本跟我们安装的jenkins版本不兼容:

可以到以下链接去把插件下载,再上传到jenkins:https://updates.jenkins-ci.org/download/plugins/

搜索Sonar,点击进去详情,我们的jenkins版本是2.361.4,所以下载2.15的版本:

下载完成后把插件上传到jenkins:

sonar生成token:

jenkins配置SonarQube服务连接及配置SonarQubeScanner工具

先配置凭据,系统管理-manager credentials-全局,创建新凭据:

jenkins系统管理处配置sonar连接:

配置SonarQubeScanner工具:

jenkins系统管理-全局工具配置:

在jenkins的job里引入SonarQube扫描

自由风格的软件项目类型的job

在原本job的基础上,构建步骤里加上Execute SonarQube Scanner:

流水线类型的job

我们的后端是java项目,用Maven构建,这里用Maven执行SonarQube扫描:

完整pipeline如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
pipeline{
agent any
options {
timestamps()
// daysToKeepStr:<Days to keep builds>, numToKeepStr:<Max # of builds to keep>
buildDiscarder(logRotator(daysToKeepStr: '7', numToKeepStr: '10'))
}
environment {
// Git Info 需修改
__Git_Addr = 'http://gitlab.xxx.com/saas-back-end/qifu-saas-basic.git'
__Git_Name = "${__Git_Addr.split('/')[-1].split('.git')[0]}"
wxbot_id = 'https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=9245d561-9d29-4d58-be39-5edc3dba293d'
}
parameters {
// TAG
//gitParameter(name:'BRANCH', type: 'PT_TAG', defaultValue: 'master', sortMode: 'DESCENDING_SMART', selectedValue: 'TOP', quickFilterEnabled: true)
gitParameter branch: '', branchFilter: '.*', defaultValue: 'master', listSize: '', name: 'BRANCH', quickFilterEnabled: true, selectedValue: 'NONE', sortMode: 'NONE', tagFilter: '*', type: 'GitParameterDefinition'
}
stages{
stage("Git Code") {
steps{
deleteDir()
script{
checkout([$class: 'GitSCM', branches: [[name: "${BRANCH}"]], extensions: [], userRemoteConfigs: [[credentialsId: 'gitlab', url: "${__Git_Addr}"]]])
}
}
}
stage("Build") {
steps{
dir("${WORKSPACE}"){
script {
sh "source /etc/profile;mvn clean package -P development -DskipTests -U"
}
}
}
}




stage('SonarQube Analysis') {
steps {
echo "开始执行 SonarQube 检测 ......"
withSonarQubeEnv('SonarQube') {
sh """
source /etc/profile;
mvn sonar:sonar \
-Dsonar.projectKey=qifu-saas-basic \
-Dsonar.java.binaries=target/classes \
"""
}
}
}



stage("Docker Build&Push&Publish") {
steps{
dir("${WORKSPACE}"){
script{
jar_name = sh (returnStdout: true, script: 'ls qifu-saas-basic-service/target/ | grep \'.jar\' | grep -v \'sources\' | head -n 1').trim()
sh """
rm Dockerfile -rf
cp /opt/data/oom-test.sh /opt/data/mc .
cp /opt/data/apache-skywalking-java-agent-9.4.0.tgz .
cp /opt/data/jacocoagent.jar .
JAR_NAME=${jar_name}
echo 'FROM ubuntu:16.04 as jar' >> Dockerfile
echo 'WORKDIR /' >> Dockerfile
echo 'RUN apt-get update -y' >> Dockerfile
echo 'RUN DEBIAN_FRONTEND=noninteractive apt-get install -y wget' >> Dockerfile
echo 'RUN wget https://repo1.maven.org/maven2/io/prometheus/jmx/jmx_prometheus_javaagent/0.20.0/jmx_prometheus_javaagent-0.20.0.jar' >> Dockerfile
echo 'FROM openjdk:11' >> Dockerfile
echo \"ADD qifu-saas-basic-service/target/\${JAR_NAME} /app/\${JAR_NAME}\" >> Dockerfile
echo 'COPY --from=jar /jmx_prometheus_javaagent-0.20.0.jar /app/jmx_prometheus_javaagent-0.20.0.jar' >> Dockerfile
echo 'COPY oom-test.sh /app/' >> Dockerfile
echo 'COPY mc /usr/local/sbin/' >> Dockerfile
echo 'ADD apache-skywalking-java-agent-9.4.0.tgz /app/' >> Dockerfile
echo 'COPY jacocoagent.jar /app/' >> Dockerfile
echo 'ENV TZ=Asia/Shanghai' >> Dockerfile
echo 'EXPOSE 8088' >> Dockerfile
echo 'EXPOSE 8913' >> Dockerfile
echo 'EXPOSE 8089' >> Dockerfile
echo 'ENTRYPOINT ["java", "-XX:+HeapDumpOnOutOfMemoryError", "-XX:HeapDumpPath=/app/dump.hprof", "-XX:+ExitOnOutOfMemoryError", "-XX:OnOutOfMemoryError=/app/oom-test.sh", "-javaagent:/app/jacocoagent.jar=includes=*,output=tcpserver,port=8089,address=0.0.0.0", "-javaagent:/app/skywalking-agent/skywalking-agent.jar", "-Dskywalking.agent.service_name=qifu-saas-basic-test", "-Dskywalking.collector.backend_service=oap.qifu.svc:11800", "-jar", "-javaagent:/app/jmx_prometheus_javaagent-0.20.0.jar=8088:/app/jmx_exporter.yaml", "/app/${jar_name}"]' >> Dockerfile
"""
sh '''
tag=`date +%s`;docker build -t harbor.keyfel.com/qifu-test/qifu-saas-basic:${VERSION}-${tag} .
docker push harbor.keyfel.com/qifu-test/qifu-saas-basic:${VERSION}-${tag}
kubectl set image deployment/qifu-saas-basic qifu-saas-basic=harbor.keyfel.com/qifu-test/qifu-saas-basic:${VERSION}-$tag -n qifu
'''
}
}
}
}
}
post {
failure{
qyWechatNotification mentionedId: '', mentionedMobile: '16620101812', moreInfo: '', webhookUrl: 'https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=9245d561-9d29-4d58-be39-5edc3dba293d'
}
}
}

如果想要选择勾选是否需要进行代码扫描,以及部署到不同的空间及配置副本数,可参考以下pipeline:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
pipeline {
agent any
tools{
maven 'mvn-3.6.3'
}

//全局变量
environment {
//Harbor仓库地址以及以及镜像所在Harbor项目
Harbor_Registry_URL= 'xxxx'
Harbor_Registry_Cert= 'xxxx'
Fat_Harbor_Registry_Project= 'xxxx'
Pro_Harbor_Registry_Project= 'xxxx'


//gitee代码仓库地址以及项目名称
Gitee_Registry_URL= 'xxxx'
Gitee_Code_Project= 'gotone-dw-api'

//-SonqrQube信息
SonarQube_URL= 'http://internal.sonarqube.com'
SonarQube_Secret= '7231ab346d6bf1875e7a6da5353a872aab25afa0'

//项目所在kubernetes命名空间(环境)
Kubernetes_Project_Namespace_fat= 'xxx'

}
parameters {
gitParameter branch: '', branchFilter: 'origin/(.*)', defaultValue: 'master', description: '选择拉取代码的分支', name: 'Branch', quickFilterEnabled: false, selectedValue: 'NONE', sortMode: 'NONE', tagFilter: '*', type: 'GitParameterDefinition'
booleanParam description: '是否进行代码质量检测;', name: 'IS_CODE_DETECTION'
booleanParam description: '是否部署到fat环境;', name: 'IS_DEPLOY_FAT'
booleanParam description: '是否部署到fat1环境;', name: 'IS_DEPLOY_FAT1'
booleanParam description: '是否将本次构建镜像推送到pro线上仓库;', name: 'Push_Image_To_Pro_Registry'
choice(name:'Replicase', choices:'1\n3\n5', description:'请选择副本数(如果对此参数不清楚的话,默认即可);' )
}

stages {
stage('拉取代码') {
steps {
xxxx
}
}

stage('质量检测') {
when { expression { return params.IS_CODE_DETECTION } }
steps {
withSonarQubeEnv('SonarQube') {
sh """
mvn sonar:sonar \
-Dsonar.projectKey=${Gitee_Code_Project} \
-Dsonar.host.url=${SonarQube_URL} \
-Dsonar.login=${SonarQube_Secret}
"""
}
}
}

stage('代码编译') {
steps {
sh """
mvn xxxx这里是正常编译代码的步骤
"""
}
}
stage('构建并上传到测试仓库') {
steps {
xxxx
xxxx
}
}

//-部署到fat环境
stage('部署到测试环境') {
when { expression { return params.IS_DEPLOY_FAT } }
steps {
xxx
}
}

stage('将本次构建的镜像传到pro仓库') {
when { expression { return params.Push_Image_To_Pro_Registry } }
steps {
xxx
}
}

stage('清除本地镜像') {
steps {
xxx
}
}
}
}

以上是通过Maven去扫描的,如果是前端的项目,也是流水线类型的job的话,在原本的流水线添加此步骤即可:

配置好之后我们构建成功后就可以在SonarQube平台看到扫描结果:

Jenkins定时扫描代码

可以把所有需要扫描的项目放到一个一个pipeline里,定时凌晨执行,如果项目太多,并行执行的话可能会导致机器资源使用率飙升导致宕机,所以需要限制最大并行数,同时如果有某个项目扫描失败不会导致整个流水线退出,而是继续执行扫描下一个项目,最后全部扫描完之后再把扫描失败的项目列出来。

前端代码扫描

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
pipeline {
agent {
label 'k8s-node4'
}
options {
timestamps()
buildDiscarder(logRotator(daysToKeepStr: '7', numToKeepStr: '10'))
timeout(time: 5, unit: 'HOURS')
}
triggers {
cron('H 2 * * 1')
}
parameters {
choice(
name: 'SCAN_PROJECTS',
choices: [
'ALL',
'boss-basic-data',
'boss-user-4pl-3pl',
'keyfil-website',
'oversea-client',
'qifu-saas-f',
'qifu-saas-m1',
'tenant-website'
],
description: '选择要扫描的项目'
)
choice(
name: 'MAX_PARALLEL',
choices: ['1', '2', '3', '4', '5'],
description: '选择最大并行任务数'
)
}
environment {
SONAR_SERVER = 'SonarQube'
WXBOT_URL = 'https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=9245d561-9d29-4d58-be39-5edc3dba293d'
}
stages {
stage('Initialize') {
steps {
script {
// 初始化失败项目列表
env.FAILED_PROJECTS = ""
}
}
}

stage('Check System Resources') {
steps {
script {
sh '''
p=($(grep "cpu " /proc/stat))
sleep 1
c=($(grep "cpu " /proc/stat))
id=$((c[4]-p[4]))
tot=$((c[1]+c[2]+c[3]+c[4]+c[5]+c[6]+c[7]+c[8]-p[1]-p[2]-p[3]-p[4]-p[5]-p[6]-p[7]-p[8]))
echo "当前CPU使用率: $((100*(tot-id)/tot))%"
'''
sh 'echo "当前内存使用率: $(free | grep Mem | awk \'{print \$3/\$2 * 100.0}\')%"'
}
}
}

stage('Multi-Project Scan') {
steps {
script {
def projectMap = [
"boss-basic-data": "http://gitlab.xxx.com/saas-front-end/boss-basic-data.git",
"boss-user-4pl-3pl": "http://gitlab.xxx.com/saas-front-end/boss-user-4pl-3pl.git",
"keyfil-website": "http://gitlab.xxx.com/saas-front-end/keyfil-website.git",
"oversea-client": "http://gitlab.xxx.com/saas-front-end/oversea-client.git",
"qifu-saas-f": "http://gitlab.xxx.com/saas-front-end/qifu-saas-f.git",
"qifu-saas-m1": "http://gitlab.xxx.com/saas-front-end/qifu-saas-m1.git",
"tenant-website": "http://gitlab.xxx.com/saas-front-end/tenant-website.git"
]

def projects = getTargetProjects(projectMap)
int maxParallel = params.MAX_PARALLEL.toInteger()
def batches = projects.collate(maxParallel)

batches.eachWithIndex { batch, index ->
stage("Batch ${index + 1}") {
def parallelStages = [:]

batch.each { projectName ->
parallelStages[projectName] = {
stage("Scan ${projectName}") {
timeout(time: 180, unit: 'MINUTES') {
try {
scanSingleProject(projectMap, projectName)
echo "✅ 项目 ${projectName} 扫描成功"
} catch (Exception e) {
echo "❌ 项目 ${projectName} 扫描失败: ${e.toString()}"
currentBuild.result = 'FAILURE'

// 更新失败项目列表
def currentFailed = env.FAILED_PROJECTS ?: ""
def newFailed = currentFailed ? "${currentFailed},${projectName}" : projectName
env.FAILED_PROJECTS = newFailed
}
}
}
}
}

parallel parallelStages
}
}
}
}
}
}
post {
always {
script {
echo "所有项目扫描完成"
sh '''
p=($(grep "cpu " /proc/stat))
sleep 1
c=($(grep "cpu " /proc/stat))
id=$((c[4]-p[4]))
tot=$((c[1]+c[2]+c[3]+c[4]+c[5]+c[6]+c[7]+c[8]-p[1]-p[2]-p[3]-p[4]-p[5]-p[6]-p[7]-p[8]))
echo "最终CPU使用率: $((100*(tot-id)/tot))%"
'''
sh 'echo "最终内存使用率: $(free | grep Mem | awk \'{print \$3/\$2 * 100.0}\')%"'
}
}
failure {
script {
echo "有项目扫描失败,发送微信通知"

// 从环境变量获取失败项目列表
def failedProjects = env.FAILED_PROJECTS ?: "无"

// 使用 echo 列出失败项目
if (failedProjects != "无") {
echo "===== 扫描失败的项目列表 ====="
failedProjects.split(',').each { project ->
echo "❌ ${project}"
}
echo "============================="
} else {
echo "没有项目被标记为失败"
}

// 构建完整的通知消息
def fullMessage = """
<font color="warning">SonarQube扫描失败通知</font>
> 最大并行数: ${params.MAX_PARALLEL}
> 失败项目: ${failedProjects}
> 构建信息: ${currentBuild.fullDisplayName}
> [查看控制台](${env.BUILD_URL}console)
"""

// 发送通知
qyWechatNotification(
mentionedId: '',
mentionedMobile: '15622091064',
moreInfo: fullMessage,
webhookUrl: env.WXBOT_URL
)
}
}
}
}

def getTargetProjects(projectMap) {
params.SCAN_PROJECTS == 'ALL' ?
projectMap.keySet() as List :
[params.SCAN_PROJECTS]
}

def scanSingleProject(projectMap, projectName) {
def gitUrl = projectMap[projectName]

dir(projectName) {
deleteDir()
checkout([$class: 'GitSCM',
branches: [[name: 'test']],
extensions: [],
userRemoteConfigs: [[credentialsId: 'gitlab', url: gitUrl]]])

script{
scannerHome = tool 'Sonar-scanner'
}
withSonarQubeEnv(env.SONAR_SERVER) {
sh """
source /etc/profile;${scannerHome}/bin/sonar-scanner \
-Dsonar.projectKey=${projectName} \
-Dsonar.sources=. \
-Dsonar.sourceEncoding=UTF-8 \
-Dsonar.inclusions=**/*.js,**/*.jsx,**/*.ts,**/*.tsx,**/*.vue \
-Dsonar.nodejs.executable=/usr/local/node-v18.20.5-linux-x64-glibc-217/bin/node \
-Dsonar.exclusions=./node_modules/,./dist/,./.git/ \
-Dsonar.analysis.analysisMode=incremental \
-Dsonar.exclusions=**/*.md,**/*.png \
-Dsonar.javascript.node.maxspace=4096
"""
}
}
}

后端代码扫描

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
pipeline {
agent {
label 'k8s-node4'
}
options {
timestamps()
buildDiscarder(logRotator(daysToKeepStr: '7', numToKeepStr: '10'))
timeout(time: 3, unit: 'HOURS')
}
triggers {
cron('H 0 * * 1')
}
parameters {
choice(
name: 'SCAN_PROJECTS',
choices: [
'ALL',
'infrastructure',
'qifu-bmp-pms',
'qifu-erp-application',
'qifu-saas-aggregation',
'qifu-saas-basic',
'qifu-saas-bc',
'qifu-saas-bpmn',
'qifu-saas-cbl-application',
'qifu-saas-cms',
'qifu-saas-cms-gateway',
'qifu-saas-crm-module',
'qifu-saas-customer-application',
'qifu-saas-data-processing',
'qifu-saas-eg',
'qifu-saas-etm',
'qifu-saas-fms',
'qifu-saas-foundation',
'qifu-saas-gateway',
'qifu-saas-inventory',
'qifu-saas-log',
'qifu-saas-m',
'qifu-saas-market',
'qifu-saas-mdm-module',
'qifu-saas-message',
'qifu-saas-oms',
'qifu-saas-org',
'qifu-saas-owms',
'qifu-saas-owms-application',
'qifu-saas-pcs',
'qifu-saas-portal',
'qifu-saas-quote',
'qifu-saas-s',
'qifu-saas-scm',
'qifu-saas-server-application',
'qifu-saas-stp',
'qifu-saas-tms',
'qifu-saas-uaa',
'qifu-saas-wechat',
'qifu-saas-wms'
],
description: '选择要扫描的项目'
)
choice(
name: 'MAX_PARALLEL',
choices: ['1', '2', '3', '4', '5'],
description: '选择最大并行任务数'
)
}
environment {
SONAR_SERVER = 'SonarQube'
WXBOT_URL = 'https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=9245d561-9d29-4d58-be39-5edc3dba293d'
}
stages {
stage('Initialize') {
steps {
script {
// 初始化失败项目列表
env.FAILED_PROJECTS = ""
}
}
}

stage('Check System Resources') {
steps {
script {
sh '''
p=($(grep "cpu " /proc/stat))
sleep 1
c=($(grep "cpu " /proc/stat))
id=$((c[4]-p[4]))
tot=$((c[1]+c[2]+c[3]+c[4]+c[5]+c[6]+c[7]+c[8]-p[1]-p[2]-p[3]-p[4]-p[5]-p[6]-p[7]-p[8]))
echo "当前CPU使用率: $((100*(tot-id)/tot))%"
'''
sh 'echo "当前内存使用率: $(free | grep Mem | awk \'{print \$3/\$2 * 100.0}\')%"'
}
}
}

stage('Multi-Project Scan') {
steps {
script {
def projectMap = [
"infrastructure": "http://gitlab.xxx.com/infrastructure/infrastructure.git",
"qifu-bmp-pms": "http://gitlab.xxx.com/saas-back-end/qifu-bmp-pms.git",
"qifu-erp-application": "http://gitlab.xxx.com/saas-back-end/qifu-erp-application.git",
"qifu-saas-aggregation": "http://gitlab.xxx.com/saas-back-end/qifu-saas-aggregation.git",
"qifu-saas-basic": "http://gitlab.xxx.com/saas-back-end/qifu-saas-basic.git",
"qifu-saas-bc": "http://gitlab.xxx.com/saas-back-end/qifu-saas-bc.git",
"qifu-saas-bpmn": "http://gitlab.xxx.com/saas-back-end/qifu-saas-bpmn.git",
"qifu-saas-cbl-application": "http://gitlab.xxx.com/saas-back-end/qifu-saas-cbl-application.git",
"qifu-saas-cms": "http://gitlab.xxx.com/saas-back-end/qifu-saas-cms.git",
"qifu-saas-cms-gateway": "http://gitlab.xxx.com/saas-back-end/qifu-saas-cms-gateway.git",
"qifu-saas-crm-module": "http://gitlab.xxx.com/saas-back-end/qifu-saas-crm-module.git",
"qifu-saas-customer-application": "http://gitlab.xxx.com/saas-back-end/qifu-saas-customer-application.git",
"qifu-saas-data-processing": "http://gitlab.xxx.com/saas-back-end/qifu-saas-data-processing.git",
"qifu-saas-eg": "http://gitlab.xxx.com/saas-back-end/qifu-saas-eg.git",
"qifu-saas-etm": "http://gitlab.xxx.com/saas-back-end/qifu-saas-etm.git",
"qifu-saas-fms": "http://gitlab.xxx.com/saas-back-end/qifu-saas-fms.git",
"qifu-saas-foundation": "http://gitlab.xxx.com/saas-back-end/qifu-saas-foundation.git",
"qifu-saas-gateway": "http://gitlab.xxx.com/saas-back-end/qifu-saas-gateway.git",
"qifu-saas-inventory": "http://gitlab.xxx.com/saas-back-end/qifu-saas-inventory.git",
"qifu-saas-log": "http://gitlab.xxx.com/saas-back-end/qifu-saas-log.git",
"qifu-saas-m": "http://gitlab.xxx.com/saas-back-end/qifu-saas-m.git",
"qifu-saas-market": "http://gitlab.xxx.com/saas-back-end/qifu-saas-market.git",
"qifu-saas-mdm-module": "http://gitlab.xxx.com/saas-back-end/qifu-saas-mdm-module.git",
"qifu-saas-message": "http://gitlab.xxx.com/saas-back-end/qifu-saas-message.git",
"qifu-saas-oms": "http://gitlab.xxx.com/saas-back-end/qifu-saas-oms.git",
"qifu-saas-org": "http://gitlab.xxx.com/saas-back-end/qifu-saas-org.git",
"qifu-saas-owms": "http://gitlab.xxx.com/saas-back-end/qifu-saas-owms.git",
"qifu-saas-owms-application": "http://gitlab.xxx.com/saas-back-end/qifu-saas-owms-application.git",
"qifu-saas-pcs": "http://gitlab.xxx.com/saas-back-end/qifu-saas-pcs.git",
"qifu-saas-portal": "http://gitlab.xxx.com/saas-back-end/qifu-saas-portal.git",
"qifu-saas-quote": "http://gitlab.xxx.com/saas-back-end/qifu-saas-quote.git",
"qifu-saas-s": "http://gitlab.xxx.com/saas-back-end/qifu-saas-s.git",
"qifu-saas-scm": "http://gitlab.xxx.com/saas-back-end/qifu-saas-scm.git",
"qifu-saas-server-application": "http://gitlab.xxx.com/saas-back-end/qifu-saas-server-application.git",
"qifu-saas-stp": "http://gitlab.xxx.com/saas-back-end/qifu-saas-stp.git",
"qifu-saas-tms": "http://gitlab.xxx.com/saas-back-end/qifu-saas-tms.git",
"qifu-saas-uaa": "http://gitlab.xxx.com/saas-back-end/qifu-saas-uaa.git",
"qifu-saas-wechat": "http://gitlab.xxx.com/saas-back-end/qifu-saas-wechat.git",
"qifu-saas-wms": "http://gitlab.xxx.com/saas-back-end/qifu-saas-wms.git"
]

def projects = getTargetProjects(projectMap)
int maxParallel = params.MAX_PARALLEL.toInteger()
def batches = projects.collate(maxParallel)

batches.eachWithIndex { batch, index ->
stage("Batch ${index + 1}") {
def parallelStages = [:]

batch.each { projectName ->
parallelStages[projectName] = {
stage("Scan ${projectName}") {
timeout(time: 60, unit: 'MINUTES') {
try {
scanSingleProject(projectMap, projectName)
echo "✅ 项目 ${projectName} 扫描成功"
} catch (Exception e) {
echo "❌ 项目 ${projectName} 扫描失败: ${e.toString()}"
currentBuild.result = 'FAILURE'

// 更新失败项目列表
def currentFailed = env.FAILED_PROJECTS ?: ""
def newFailed = currentFailed ? "${currentFailed},${projectName}" : projectName
env.FAILED_PROJECTS = newFailed
}
}
}
}
}

parallel parallelStages
}
}
}
}
}
}
post {
always {
script {
echo "所有项目扫描完成"
sh '''
p=($(grep "cpu " /proc/stat))
sleep 1
c=($(grep "cpu " /proc/stat))
id=$((c[4]-p[4]))
tot=$((c[1]+c[2]+c[3]+c[4]+c[5]+c[6]+c[7]+c[8]-p[1]-p[2]-p[3]-p[4]-p[5]-p[6]-p[7]-p[8]))
echo "最终CPU使用率: $((100*(tot-id)/tot))%"
'''
sh 'echo "最终内存使用率: $(free | grep Mem | awk \'{print \$3/\$2 * 100.0}\')%"'
}
}
failure {
script {
echo "有项目扫描失败,发送微信通知"

// 从环境变量获取失败项目列表
def failedProjects = env.FAILED_PROJECTS ?: "无"

// 使用 echo 列出失败项目
if (failedProjects != "无") {
echo "===== 扫描失败的项目列表 ====="
failedProjects.split(',').each { project ->
echo "❌ ${project}"
}
echo "============================="
} else {
echo "没有项目被标记为失败"
}

// 构建完整的通知消息
def fullMessage = """
<font color="warning">SonarQube扫描失败通知</font>
> 最大并行数: ${params.MAX_PARALLEL}
> 失败项目: ${failedProjects}
> 构建信息: ${currentBuild.fullDisplayName}
> [查看控制台](${env.BUILD_URL}console)
"""

// 发送通知
qyWechatNotification(
mentionedId: '',
mentionedMobile: '15622091064',
moreInfo: fullMessage,
webhookUrl: env.WXBOT_URL
)
}
}
}
}

def getTargetProjects(projectMap) {
params.SCAN_PROJECTS == 'ALL' ?
projectMap.keySet() as List :
[params.SCAN_PROJECTS]
}

def scanSingleProject(projectMap, projectName) {
def gitUrl = projectMap[projectName]

dir(projectName) {
deleteDir()
checkout([$class: 'GitSCM',
branches: [[name: 'test']],
extensions: [],
userRemoteConfigs: [[credentialsId: 'gitlab', url: gitUrl]]])

sh """
source /etc/profile
export MAVEN_OPTS="-Xmx1024m -XX:MaxRAMPercentage=75.0"
mvn clean package -P development -DskipTests -U -T 1C
"""

withSonarQubeEnv(env.SONAR_SERVER) {
sh """
source /etc/profile
export MAVEN_OPTS="-Xmx1024m -XX:MaxRAMPercentage=75.0"
mvn sonar:sonar \
-Dsonar.projectKey=${projectName} \
-Dsonar.java.binaries=target/classes \
-Dsonar.sourceEncoding=UTF-8 \
-Dsonar.analysis.analysisMode=incremental \
-Dsonar.exclusions=**/*.md,**/*.png \
-T 1C
"""
}
}
}

其他配置

sonarqube是用java启动的,默认-xmx有三个,都是512M,(search是es)

1
2
3
4
5
#sonar.web.javaOpts=-Xmx512m -Xms128m -XX:+HeapDumpOnOutOfMemoryError

#sonar.ce.javaOpts=-Xmx512m -Xms128m -XX:+HeapDumpOnOutOfMemoryError

#sonar.search.javaOpts=-Xmx512m -Xms512m -XX:MaxDirectMemorySize=256m -XX:+HeapDumpOnOutOfMemoryError

需要改的话可以在sonar.properties修改然后重启SonarQube服务:

jenkins的插件sonarscanner需要修改-xmx的话可以在JVM Options加:

问题

扫描前端项目时,特别慢,排查到是因为node执行的eslint特别消耗CPU,CPU飙到99:

Thank you for your accept. mua!
-------------本文结束感谢您的阅读-------------