├── .deploy ├── Dockerfile ├── Jenkinsfile ├── dev-k8s.yaml ├── prod-k8s.yaml ├── service.sh └── test-k8s.yaml ├── .gitignore ├── .mvn └── wrapper │ ├── MavenWrapperDownloader.java │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── Jenkinsfile ├── config ├── checkstyle │ ├── checkstyle.md │ ├── google-checks-8.29.xml │ └── intellij-java-google-style.xml └── findbugs │ └── findbugs-exclude.xml ├── mvnw ├── mvnw.cmd ├── pom.xml ├── readme.md ├── sonar-project.properties └── src ├── main ├── java │ └── com │ │ └── example │ │ └── codequalityverifydemo │ │ ├── CodeQualityVerifyDemoApplication.java │ │ ├── TestController.java │ │ ├── TestController2.java │ │ └── domain │ │ └── service │ │ ├── ITestService.java │ │ └── impl │ │ └── TestService.java └── resources │ └── application.properties └── test └── java └── com └── example └── codequalityverifydemo └── CodeQualityVerifyDemoApplicationTests.java /.deploy/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM apache/skywalking-base:6.6.0-es7 as skywalking 2 | FROM mydocker-registry.com/centos7-jre8 3 | VOLUME /tmp 4 | ARG profile 5 | ENV SPRING_PROFILES_ACTIVE ${profile} 6 | 7 | ARG JAR_FILE 8 | 9 | COPY ${JAR_FILE} /app.jar 10 | COPY --from=skywalking /skywalking/agent /skywalking/agent 11 | 12 | ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom", "-jar", "-javaagent:/skywalking/agent/skywalking-agent.jar", "-jar","/app.jar"] 13 | -------------------------------------------------------------------------------- /.deploy/Jenkinsfile: -------------------------------------------------------------------------------- 1 | class MyChange { 2 | String author; 3 | String msg; 4 | } 5 | 6 | @NonCPS 7 | def getChanges() { 8 | def changeLogSets = currentBuild.changeSets 9 | for (int i = 0; i < changeLogSets.size(); i++) { 10 | def entries = changeLogSets[0].items 11 | for (int j = 0; j < entries.length; j++) { 12 | def entry = entries[0] 13 | def change = new MyChange() 14 | change.author = entry.author 15 | change.msg = entry.msg 16 | return change 17 | } 18 | } 19 | 20 | } 21 | 22 | node { 23 | properties([gitLabConnection('gitlab-bigdata')]) 24 | 25 | stage('Prepare') { 26 | echo "1.Prepare Stage" 27 | checkout scm 28 | updateGitlabCommitStatus name: 'build', state: 'pending' 29 | mvn_module = "." 30 | module_path = "${mvn_module}" 31 | pom = readMavenPom file: "./${module_path}/pom.xml" 32 | module_parent = pom.parent.artifactId 33 | module_group = pom.parent.groupId 34 | module_artifactId = pom.artifactId 35 | module_version = pom.parent.version 36 | k8s_label = mvn_module 37 | docker_host = "mydocker-registry.com" 38 | ding_group_access_token = "faf8e44b7aeb99your-ding-group-token-f3b9676f0224e580c84bddff79013f14a" 39 | ding_jenkinsUrl = "http://jenkins.ryan-miao.com/view/%E5%BC%80%E5%BA%97%E5%8A%A9%E6%89%8B/" 40 | //要部署的k8s集群, 默认是杭州(config-hangzhou), 可选上海(config-shanghai) 41 | //部署环境 42 | profile = "" 43 | if (env.BRANCH_NAME == 'test') { 44 | profile = "test" 45 | k8s_cluster_node = "config-ryan-test" 46 | } 47 | if (env.BRANCH_NAME == 'master') { 48 | profile = "prod" 49 | k8s_cluster_node = "config-ryan-prod" 50 | } 51 | 52 | img_name = "${module_group}/${module_artifactId}" 53 | docker_img_name = "${docker_host}/${img_name}" 54 | echo "group: ${module_group}, artifactId: ${module_artifactId}, version: ${module_version}" 55 | echo "docker-img-name: ${docker_img_name}" 56 | script { 57 | build_tag = sh(returnStdout: true, script: 'git rev-parse --short HEAD').trim() 58 | build_tag = "${env.BRANCH_NAME}-${build_tag}" 59 | 60 | currentBuild.displayName = BUILD_NUMBER + "_" + build_tag 61 | } 62 | } 63 | 64 | stage('Compile And UnitTest') { 65 | echo "2.Compile the code" 66 | try { 67 | sh "source /etc/profile; mvn clean install" 68 | } catch (Exception ex) { 69 | updateGitlabCommitStatus name: 'build', state: 'failed' 70 | def change = getChanges() 71 | dingTalk accessToken: "${ding_group_access_token}", imageUrl: '', jenkinsUrl: "${ding_jenkinsUrl}", message: "@所有人 构建失败@$change.author $change.msg", notifyPeople: "$change.author" 72 | throw ex; 73 | } finally { 74 | 75 | } 76 | 77 | updateGitlabCommitStatus name: 'build', state: 'success' 78 | } 79 | 80 | 81 | if (env.BRANCH_NAME == 'dev' || env.BRANCH_NAME == 'test' || env.BRANCH_NAME == 'master') { 82 | 83 | echo "发布api" 84 | if(module_version.contains('-SNAPSHOT')) { 85 | sh "mvn deploy" 86 | } 87 | if (env.BRANCH_NAME == 'dev') { 88 | def remote = [:] 89 | remote.name = 'k8s-worker-003' 90 | remote.host = '10.0.100.23' 91 | remote.user = 'root' 92 | remote.password = 'sw@2019' 93 | remote.allowAnyHosts = true 94 | // bind到机器的端口 95 | deploy_nodePort = 31325 96 | // springboot内部的端口 97 | deploy_serverPort = 31325 98 | // deploy dir 99 | deploy_dir = "/home/deploy/${module_artifactId}/" 100 | stage('Remote SSH') { 101 | sshCommand remote: remote, command: "mkdir -p ${deploy_dir}" 102 | sshPut remote: remote, from: "${WORKSPACE}/${module_path}/.deploy/service.sh", into: "${deploy_dir}" 103 | sshCommand remote: remote, command: "chmod +x ${deploy_dir}service.sh" 104 | sshPut remote: remote, from: "${WORKSPACE}/${module_path}/target/${module_artifactId}.jar", into: "${deploy_dir}" 105 | sshCommand remote: remote, command: "cd ${deploy_dir}; /bin/bash service.sh ${module_artifactId} restart ${deploy_nodePort} dev" 106 | } 107 | updateGitlabCommitStatus name: 'deploy', state: 'success' 108 | } 109 | 110 | if (env.BRANCH_NAME == 'test' || env.BRANCH_NAME == 'master') { 111 | stage('Build Docker Image') { 112 | echo "4.Build Docker Image Stage" 113 | sh "docker build -t ${docker_img_name}:${build_tag} " + 114 | " --build-arg JAR_FILE=target/${mvn_module}.jar " + 115 | " --build-arg profile=${profile} " + 116 | " -f ${module_path}/.deploy/Dockerfile ./${module_path}" 117 | } 118 | 119 | stage('Push Docker Image') { 120 | echo "5.Push Docker Image Stage" 121 | //sh "mvn deploy -Dmaven.test.skip=true" 122 | sh "docker tag ${docker_img_name}:${build_tag} ${docker_img_name}:latest" 123 | sh "docker tag ${docker_img_name}:${build_tag} ${docker_img_name}:${pom.version}" 124 | withCredentials([usernamePassword(credentialsId: 'docker-register-ryan-miao', passwordVariable: 'dockerPassword', usernameVariable: 'dockerUser')]) { 125 | sh "docker login -u ${dockerUser} -p ${dockerPassword} ${docker_host}" 126 | sh "docker push ${docker_img_name}:latest" 127 | sh "docker push ${docker_img_name}:${pom.version}" 128 | sh "docker push ${docker_img_name}:${build_tag}" 129 | } 130 | } 131 | 132 | stage("Deploy to k8s - ${profile}") { 133 | echo "6. Deploy Stage" 134 | 135 | updateGitlabCommitStatus name: 'deploy', state: 'pending' 136 | def k8s_conf_yaml = "${WORKSPACE}/${module_path}/.deploy/${profile}-k8s.yaml" 137 | def k8s_cluster_node_conf = "/home/jenkins/.kube/${k8s_cluster_node}" 138 | 139 | sh "sed -i 's!!${img_name}!g;s!!${build_tag}!g;s!!${k8s_label}!g' ${k8s_conf_yaml} " 140 | sh "kubectl --kubeconfig ${k8s_cluster_node_conf} apply -f ${k8s_conf_yaml} --record" 141 | sh "sleep 5" 142 | echo "创建的实例:" 143 | 144 | sh " kubectl --kubeconfig ${k8s_cluster_node_conf} get po -o wide | grep ${k8s_label}" 145 | echo "您的应用svc: " 146 | sh " kubectl --kubeconfig ${k8s_cluster_node_conf} get svc | grep ${k8s_label}" 147 | 148 | updateGitlabCommitStatus name: 'deploy', state: 'success' 149 | if (profile == "prod") { 150 | dingSuccess() 151 | } 152 | } 153 | } 154 | } 155 | 156 | 157 | } 158 | 159 | 160 | private void dingSuccess() { 161 | echo "构建成功, 发布生产环境" 162 | dingTalk accessToken: "${ding_group_access_token}", imageUrl: '', jenkinsUrl: "${ding_jenkinsUrl}", message: "@所有人 构建成功, 发布生产环境", notifyPeople: "" 163 | } 164 | 165 | -------------------------------------------------------------------------------- /.deploy/dev-k8s.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1beta2 2 | kind: Deployment 3 | metadata: 4 | annotations: 5 | deployment.kubernetes.io/revision: '8' 6 | generation: 20 7 | labels: 8 | app: 9 | name: 10 | namespace: default 11 | spec: 12 | progressDeadlineSeconds: 600 13 | replicas: 1 14 | revisionHistoryLimit: 10 15 | selector: 16 | matchLabels: 17 | app: 18 | strategy: 19 | rollingUpdate: 20 | maxSurge: 25% 21 | maxUnavailable: 25% 22 | type: RollingUpdate 23 | template: 24 | metadata: 25 | labels: 26 | app: 27 | spec: 28 | containers: 29 | - image: "mydocker-registry.com/:" 30 | imagePullPolicy: Always 31 | name: 32 | ports: 33 | - containerPort: 8080 34 | resources: 35 | requests: 36 | cpu: 250m 37 | memory: 512Mi 38 | terminationMessagePath: /dev/termination-log 39 | terminationMessagePolicy: File 40 | dnsPolicy: ClusterFirst 41 | imagePullSecrets: 42 | - name: docker-login-secrets 43 | restartPolicy: Always 44 | schedulerName: default-scheduler 45 | securityContext: {} 46 | terminationGracePeriodSeconds: 30 47 | 48 | 49 | --- 50 | 51 | apiVersion: v1 52 | kind: Service 53 | metadata: 54 | annotations: 55 | service.beta.kubernetes.io/alicloud-loadbalancer-address-type: intranet 56 | name: 57 | namespace: default 58 | spec: 59 | externalTrafficPolicy: Cluster 60 | ports: 61 | - port: 80 62 | protocol: TCP 63 | selector: 64 | app: 65 | sessionAffinity: None 66 | type: NodePort 67 | 68 | -------------------------------------------------------------------------------- /.deploy/prod-k8s.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1beta2 2 | kind: Deployment 3 | metadata: 4 | annotations: 5 | deployment.kubernetes.io/revision: '8' 6 | generation: 20 7 | labels: 8 | app: 9 | name: 10 | namespace: default 11 | spec: 12 | progressDeadlineSeconds: 600 13 | replicas: 2 14 | revisionHistoryLimit: 10 15 | selector: 16 | matchLabels: 17 | app: 18 | strategy: 19 | rollingUpdate: 20 | maxSurge: 25% 21 | maxUnavailable: 25% 22 | type: RollingUpdate 23 | template: 24 | metadata: 25 | labels: 26 | app: 27 | spec: 28 | containers: 29 | - image: "mydocker-registry.com/:" 30 | imagePullPolicy: Always 31 | name: 32 | ports: 33 | - containerPort: 8080 34 | resources: 35 | requests: 36 | cpu: 250m 37 | memory: 512Mi 38 | terminationMessagePath: /dev/termination-log 39 | terminationMessagePolicy: File 40 | env: 41 | - name: aliyun_logs_ryan-miao-access-prod 42 | value: "/tmp/logs/ryan-miao-app*.log" 43 | - name: aliyun_logs_ryan-miao-access-prod_format 44 | value: "json" 45 | - name: aliyun_logs_ryan-miao-access-prod_tags 46 | value: "service=,env=prod" 47 | - name: SW_AGENT_NAMESPACE 48 | value: "ryan-miao-prod" 49 | - name: SW_AGENT_COLLECTOR_BACKEND_SERVICES 50 | value: "192.168.81.41:11800" 51 | - name: SW_AGENT_NAME 52 | value: "" 53 | - name: SW_AGENT_SAMPLE 54 | value: "0" 55 | volumeMounts: 56 | - name: app-log 57 | mountPath: /tmp/logs 58 | volumes: 59 | - name: app-log 60 | emptyDir: {} 61 | dnsPolicy: ClusterFirst 62 | imagePullSecrets: 63 | - name: docker-login-secrets 64 | restartPolicy: Always 65 | schedulerName: default-scheduler 66 | securityContext: {} 67 | terminationGracePeriodSeconds: 30 68 | 69 | 70 | -------------------------------------------------------------------------------- /.deploy/service.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | #这里可替换为你自己的执行程序,其他代码无需更改 3 | source /etc/profile 4 | 5 | #使用说明,用来提示输入参数 6 | usage() { 7 | echo "Usage: sh service.sh [jar包名称] [start|stop|restart] [port] [env]" 8 | echo "Usage: sh service.sh code-quality-veryfy-demo start 4000 dev" 9 | exit 1 10 | } 11 | 12 | if [ $# -eq 0 ]; 13 | then 14 | usage 15 | fi 16 | PROJECT_NAME="" 17 | if [ ! $1 ]; then 18 | echo "待执行的jar名称 IS NULL" 19 | exit 1 20 | else 21 | PROJECT_NAME=$1".jar" 22 | fi 23 | EXECUTE_TYPE="" 24 | if [ ! $2 ]; then 25 | echo "执行类型 [start|stop|restart] IS NULL" 26 | exit 1 27 | else 28 | EXECUTE_TYPE=$2 29 | fi 30 | EXECUTE_PORT="" 31 | if [ ! $3 ] && [ "$EXECUTE_TYPE" != "stop" ]; then 32 | echo "端口号 IS NULL" 33 | exit 1 34 | else 35 | EXECUTE_PORT=$3 36 | fi 37 | EXECUTE_ENV="" 38 | if [ ! $4 ] && [ "$EXECUTE_TYPE" != "stop" ]; then 39 | echo "执行环境 IS NULL" 40 | exit 1 41 | else 42 | EXECUTE_ENV=$4 43 | fi 44 | 45 | echo "接收到的参数如下:PROJECT_NAME="$PROJECT_NAME ",EXECUTE_TYPE="$EXECUTE_TYPE ",EXECUTE_PORT="$EXECUTE_PORT "EXECUTE_ENV="$EXECUTE_ENV 46 | 47 | echo "2、判断进程是否存在" 48 | CURRENT_THREAD_COUNT=`ps -ef | grep $PROJECT_NAME | grep $PROJECT_NAME | grep -v "grep" |wc -l` 49 | 50 | #echo "run command : ps -ef | grep $PROCESS_PID | grep $PROCESS_PID | grep -v "grep" |wc -l" 51 | echo "当前"$PROJECT_NAME"的进程个数为:"$CURRENT_THREAD_COUNT 52 | PROJECT_THREAD_PID=`ps -ef |grep "$PROJECT_NAME" |grep "$PROJECT_NAME" |grep -v "grep" |awk '{print $2}'` 53 | 54 | echo "当前"$PROJECT_NAME"的进程ID="$PROJECT_THREAD_PID 55 | 56 | #CURRENT_THREAD_PID=$(netstat -nlp | grep :$EXECUTE_PORT | awk '{print $7}' | awk -F"/" '{ print $1 }'); 57 | CURRENT_THREAD_PID= 58 | if [ -n "$EXECUTE_PORT" ];then 59 | CURRENT_THREAD_PID=$(netstat -nlp | grep :$EXECUTE_PORT | awk '{print $7}' |sed 's/\([0-9]*\).*/\1/g'); 60 | echo "执行shell命令:netstat -nlp | grep :$EXECUTE_PORT | awk '{print $7}' |sed 's/\([0-9]*\).*/\1/g'" 61 | echo "根据端口号 $EXECUTE_PORT 获取的进程号为 $CURRENT_THREAD_PID" 62 | else 63 | echo "端口号为空,不执行根据端口号获取进程ID的命令。" 64 | fi 65 | 66 | #启动方法 67 | start(){ 68 | echo "3、启动服务" 69 | if [ "$EXECUTE_TYPE" == "restart" ]; then 70 | CURRENT_THREAD_PID=$(netstat -nlp | grep :$EXECUTE_PORT | awk '{print $7}' |sed 's/\([0-9]*\).*/\1/g'); 71 | fi 72 | 73 | if [ -n "$CURRENT_THREAD_PID" ]; then 74 | echo "服务名称:$PROJECT_NAME ,端口号为:$EXECUTE_PORT ,进程号为:$CURRENT_THREAD_PID 的服务正在运行中....." 75 | echo "本次启动请求拒绝执行." 76 | exit 1 77 | fi 78 | 79 | echo -n "开始启动 $PROJECT_NAME" 80 | nohup java -server -jar -Dserver.port=$EXECUTE_PORT -Dspring.profiles.active=$EXECUTE_ENV -XX:MaxMetaspaceSize=256m -Xmx2048m -Xss256k -XX:SurvivorRatio=8 $PROJECT_NAME > /dev/null 2>&1 & 81 | 82 | for st in $(seq 1 20) 83 | do 84 | # PID=$(netstat -nlp | grep :$EXECUTE_PORT | awk '{print $7}' | awk -F"/" '{ print $1 }'); 85 | PID=$(netstat -nlp | grep :$EXECUTE_PORT | awk '{print $7}' |sed 's/\([0-9]*\).*/\1/g'); 86 | if [ $st -eq 20 ] && [ -z "$PID" ]; then 87 | echo "服务启动失败" break 88 | fi 89 | 90 | if [ -z "$PID" ]; then 91 | sleep 3 92 | echo $st"服务启动中...." else 93 | echo "服务名称:$PROJECT_NAME ,端口号为:$EXECUTE_PORT ,进程号为:$PID 启动成功 , 耗时:$[$[st-1]*3] seconds!!!" 94 | break 95 | fi 96 | 97 | done 98 | } 99 | 100 | stop(){ 101 | echo "开始执行停止服务命令!!!" 102 | if [ -z "$CURRENT_THREAD_PID" ] && [ -z "$PROJECT_THREAD_PID" ];then 103 | echo "端口号或者服务名称均不正确,请修正后重试!!!" 104 | fi 105 | 106 | if [ -n "$CURRENT_THREAD_PID" ]; then 107 | kill -9 $CURRENT_THREAD_PID 108 | echo "根据端口号【$EXECUTE_PORT】停止进程【$CURRENT_THREAD_PID】成功!!!" 109 | else 110 | if [ -n "$PROJECT_THREAD_PID" ]; then 111 | if [ $CURRENT_THREAD_COUNT -ne 1 ]; then 112 | echo "批量执行进程 kill 命令" 113 | for tpid in $PROJECT_THREAD_PID 114 | do 115 | kill -9 $tpid 116 | echo "根据服务名称【$PROJECT_NAME】 停止进程【$tpid】成功!!!" 117 | done 118 | else 119 | kill -9 $PROJECT_THREAD_PID 120 | echo "根据服务名称【$PROJECT_NAME】 停止进程【$PROJECT_THREAD_PID】成功!!!" 121 | fi 122 | fi 123 | fi 124 | } 125 | restart(){ 126 | stop 127 | start 128 | } 129 | case "$EXECUTE_TYPE" in 130 | "start") 131 | start 132 | ;; 133 | "stop") 134 | stop 135 | ;; 136 | "status") 137 | status 138 | ;; 139 | "restart") 140 | restart 141 | ;; 142 | *) 143 | usage 144 | ;; 145 | esac -------------------------------------------------------------------------------- /.deploy/test-k8s.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1beta2 2 | kind: Deployment 3 | metadata: 4 | annotations: 5 | deployment.kubernetes.io/revision: '8' 6 | generation: 20 7 | labels: 8 | app: 9 | name: 10 | namespace: default 11 | spec: 12 | progressDeadlineSeconds: 600 13 | replicas: 2 14 | revisionHistoryLimit: 10 15 | selector: 16 | matchLabels: 17 | app: 18 | strategy: 19 | rollingUpdate: 20 | maxSurge: 25% 21 | maxUnavailable: 25% 22 | type: RollingUpdate 23 | template: 24 | metadata: 25 | labels: 26 | app: 27 | spec: 28 | containers: 29 | - image: "mydocker-registry.com/:" 30 | imagePullPolicy: Always 31 | name: 32 | ports: 33 | - containerPort: 8080 34 | resources: 35 | requests: 36 | cpu: 250m 37 | memory: 512Mi 38 | terminationMessagePath: /dev/termination-log 39 | terminationMessagePolicy: File 40 | env: 41 | - name: aliyun_logs_ryan-miao-access 42 | value: "/tmp/logs/ryan-miao-app*.log" 43 | - name: aliyun_logs_ryan-miao-access_format 44 | value: "json" 45 | - name: aliyun_logs_ryan-miao-access_tags 46 | value: "service=,env=test" 47 | - name: SW_AGENT_NAMESPACE 48 | value: "ryan-miao-test" 49 | - name: SW_AGENT_COLLECTOR_BACKEND_SERVICES 50 | value: "192.168.80.141:11800" 51 | - name: SW_AGENT_NAME 52 | value: "" 53 | - name: SW_AGENT_SAMPLE 54 | value: "0" 55 | volumeMounts: 56 | - name: app-log 57 | mountPath: /tmp/logs 58 | volumes: 59 | - name: app-log 60 | emptyDir: {} 61 | dnsPolicy: ClusterFirst 62 | imagePullSecrets: 63 | - name: docker-login-secrets 64 | restartPolicy: Always 65 | schedulerName: default-scheduler 66 | securityContext: {} 67 | terminationGracePeriodSeconds: 30 68 | 69 | 70 | --- 71 | 72 | apiVersion: v1 73 | kind: Service 74 | metadata: 75 | annotations: 76 | service.beta.kubernetes.io/alicloud-loadbalancer-address-type: intranet 77 | name: 78 | namespace: default 79 | spec: 80 | externalTrafficPolicy: Cluster 81 | ports: 82 | - port: 80 83 | protocol: TCP 84 | selector: 85 | app: 86 | sessionAffinity: None 87 | type: LoadBalancer 88 | 89 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | /target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | 5 | ### STS ### 6 | .apt_generated 7 | .classpath 8 | .factorypath 9 | .project 10 | .settings 11 | .springBeans 12 | .sts4-cache 13 | 14 | ### IntelliJ IDEA ### 15 | .idea 16 | *.iws 17 | *.iml 18 | *.ipr 19 | 20 | ### NetBeans ### 21 | /nbproject/private/ 22 | /nbbuild/ 23 | /dist/ 24 | /nbdist/ 25 | /.nb-gradle/ 26 | /build/ 27 | 28 | ### VS Code ### 29 | .vscode/ 30 | -------------------------------------------------------------------------------- /.mvn/wrapper/MavenWrapperDownloader.java: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | https://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | import java.io.File; 21 | import java.io.FileInputStream; 22 | import java.io.FileOutputStream; 23 | import java.io.IOException; 24 | import java.net.URL; 25 | import java.nio.channels.Channels; 26 | import java.nio.channels.ReadableByteChannel; 27 | import java.util.Properties; 28 | 29 | public class MavenWrapperDownloader { 30 | 31 | /** 32 | * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. 33 | */ 34 | private static final String DEFAULT_DOWNLOAD_URL = 35 | "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar"; 36 | 37 | /** 38 | * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to use 39 | * instead of the default one. 40 | */ 41 | private static final String MAVEN_WRAPPER_PROPERTIES_PATH = 42 | ".mvn/wrapper/maven-wrapper.properties"; 43 | 44 | /** 45 | * Path where the maven-wrapper.jar will be saved to. 46 | */ 47 | private static final String MAVEN_WRAPPER_JAR_PATH = 48 | ".mvn/wrapper/maven-wrapper.jar"; 49 | 50 | /** 51 | * Name of the property which should be used to override the default download url for the 52 | * wrapper. 53 | */ 54 | private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; 55 | 56 | public static void main(String args[]) { 57 | System.out.println("- Downloader started"); 58 | File baseDirectory = new File(args[0]); 59 | System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); 60 | 61 | // If the maven-wrapper.properties exists, read it and check if it contains a custom 62 | // wrapperUrl parameter. 63 | File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); 64 | String url = DEFAULT_DOWNLOAD_URL; 65 | if (mavenWrapperPropertyFile.exists()) { 66 | FileInputStream mavenWrapperPropertyFileInputStream = null; 67 | try { 68 | mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); 69 | Properties mavenWrapperProperties = new Properties(); 70 | mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); 71 | url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); 72 | } catch (IOException e) { 73 | System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); 74 | } finally { 75 | try { 76 | if (mavenWrapperPropertyFileInputStream != null) { 77 | mavenWrapperPropertyFileInputStream.close(); 78 | } 79 | } catch (IOException e) { 80 | // Ignore ... 81 | } 82 | } 83 | } 84 | System.out.println("- Downloading from: : " + url); 85 | 86 | File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); 87 | if (!outputFile.getParentFile().exists()) { 88 | if (!outputFile.getParentFile().mkdirs()) { 89 | System.out.println( 90 | "- ERROR creating output direcrory '" + outputFile.getParentFile() 91 | .getAbsolutePath() + "'"); 92 | } 93 | } 94 | System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); 95 | try { 96 | downloadFileFromURL(url, outputFile); 97 | System.out.println("Done"); 98 | System.exit(0); 99 | } catch (Throwable e) { 100 | System.out.println("- Error downloading"); 101 | e.printStackTrace(); 102 | System.exit(1); 103 | } 104 | } 105 | 106 | private static void downloadFileFromURL(String urlString, File destination) throws Exception { 107 | URL website = new URL(urlString); 108 | ReadableByteChannel rbc; 109 | rbc = Channels.newChannel(website.openStream()); 110 | FileOutputStream fos = new FileOutputStream(destination); 111 | fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); 112 | fos.close(); 113 | rbc.close(); 114 | } 115 | 116 | } 117 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ryan-Miao/code-quality-verify-demo/72a2cb5fa4f0065752ccf91a96b6329b0d00a509/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.0/apache-maven-3.6.0-bin.zip 2 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | node('slave001') { 2 | 3 | properties([gitLabConnection('gitlab-bigdata')]) 4 | 5 | stage('Prepare') { 6 | echo "1.Prepare Stage" 7 | 8 | checkout scm 9 | updateGitlabCommitStatus name: 'build', state: 'pending' 10 | 11 | project_module = '.' 12 | pom = readMavenPom file: "${project_module}/pom.xml" 13 | echo "group: ${pom.groupId}, artifactId: ${pom.artifactId}, version: ${pom.version}" 14 | script { 15 | build_tag = sh(returnStdout: true, script: 'git rev-parse --short HEAD').trim() 16 | build_tag = "${env.BRANCH_NAME}-${build_tag}" 17 | // if (env.BRANCH_NAME != 'master' && env.BRANCH_NAME != null) { 18 | // build_tag = "${env.BRANCH_NAME}-${build_tag}" 19 | // } 20 | 21 | currentBuild.displayName = BUILD_NUMBER + "_" +build_tag 22 | } 23 | } 24 | 25 | stage('Compile And UnitTest') { 26 | echo "2.Compile the code" 27 | 28 | try { 29 | sh "mvn clean install" 30 | junit testResults: '**/target/*-reports/TEST-*.xml' 31 | jacoco() 32 | } catch(Exception ex){ 33 | updateGitlabCommitStatus name: 'build', state: 'failed' 34 | throw ex; 35 | } finally { 36 | 37 | } 38 | updateGitlabCommitStatus name: 'build', state: 'success' 39 | updateGitlabCommitStatus name: 'Basic Quality Check', state: 'pending' 40 | } 41 | 42 | 43 | stage('Basic Quality Report') { 44 | echo "3.Basic quality report" 45 | sh "mvn site " 46 | 47 | def java = scanForIssues tool: java() 48 | def javadoc = scanForIssues tool: javaDoc() 49 | 50 | publishIssues id: 'analysis-java', name: 'Java Issues', issues: [java, javadoc] //, filters: [includePackage('io.jenkins.plugins.analysis.*')] 51 | 52 | def checkstyle = scanForIssues tool: checkStyle(pattern: '**/target/checkstyle-result.xml') 53 | publishIssues issues: [checkstyle] 54 | 55 | def pmd = scanForIssues tool: pmdParser(pattern: '**/target/pmd.xml') 56 | publishIssues issues: [pmd] 57 | 58 | def cpd = scanForIssues tool: cpd(pattern: '**/target/cpd.xml') 59 | publishIssues issues: [cpd] 60 | 61 | def spotbugs = scanForIssues tool: spotBugs(pattern: '**/target/findbugsXml.xml') 62 | publishIssues issues: [spotbugs] 63 | 64 | def maven = scanForIssues tool: mavenConsole() 65 | publishIssues issues: [maven] 66 | 67 | publishIssues id: 'analysis-all', name: 'All Issues', 68 | issues: [checkstyle, pmd, spotbugs] //, filters: [includePackage('io.jenkins.plugins.analysis.*')] 69 | } 70 | 71 | stage('Basic Quality Check') { 72 | echo "3.1 Check quality threshold" 73 | 74 | try { 75 | echo "Just skip check for demo, but should check when work" 76 | //sh "mvn pmd:check pmd:cpd checkstyle:check findbugs:check" 77 | } catch(Exception ex){ 78 | updateGitlabCommitStatus name: 'Basic Quality Check', state: 'failed' 79 | throw ex; 80 | } finally { 81 | 82 | } 83 | updateGitlabCommitStatus name: 'Basic Quality Check', state: 'success' 84 | } 85 | 86 | 87 | stage('SonarQube analysis') { 88 | 89 | updateGitlabCommitStatus name: 'SonarQube analysis', state: 'pending' 90 | def sonarqubeScannerHome = tool name: 'SonarQube Scanner' 91 | 92 | withSonarQubeEnv('SonarQube') { 93 | sh "${sonarqubeScannerHome}/bin/sonar-scanner -Dproject.settings=./sonar-project.properties" 94 | } 95 | 96 | } 97 | 98 | // No need to occupy a node 99 | stage("SonarQube Quality Gate"){ 100 | timeout(time: 1, unit: 'MINUTES') { // Just in case something goes wrong, pipeline will be killed after a timeout 101 | def qg = waitForQualityGate() // Reuse taskId previously collected by withSonarQubeEnv 102 | if (qg.status != 'OK') { 103 | updateGitlabCommitStatus name: 'SonarQube analysis', state: 'failed' 104 | error "Pipeline aborted due to quality gate failure: ${qg.status}" 105 | } else { 106 | updateGitlabCommitStatus name: 'SonarQube analysis', state: 'success' 107 | } 108 | } 109 | } 110 | 111 | if (env.BRANCH_NAME == 'master' || env.BRANCH_NAME == 'dev' ) { 112 | stage("Build Docker Image"){ 113 | echo "build docker image" 114 | echo "Only dev/master branch can build docker image" 115 | } 116 | 117 | if(env.BRANCH_NAME == 'dev'){ 118 | stage("Deploy to test"){ 119 | echo "branch dev to deploy to environment test" 120 | } 121 | 122 | stage("Integration test"){ 123 | echo "test环境集成测试" 124 | } 125 | 126 | } 127 | 128 | if(env.BRANCH_NAME == 'master'){ 129 | stage("Deploy to prod"){ 130 | echo "branch master to deploy to environment prod" 131 | } 132 | 133 | stage("Health check"){ 134 | echo "prod检查" 135 | } 136 | 137 | } 138 | } 139 | 140 | 141 | } 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | -------------------------------------------------------------------------------- /config/checkstyle/checkstyle.md: -------------------------------------------------------------------------------- 1 | checkstyle使用方法 2 | ===== 3 | 4 | 5 | 我们的项目需要有代码规范,checkstyle就是检查代码规范的工具。 6 | 7 | ## checkstyle 规则配置文件 8 | 9 | google-checks-6.18.xml 10 | 11 | 此文件定义了规则,所以修改前需要争取大家的同意,通常不要修改。 12 | 13 | 14 | 15 | ## idea插件 16 | 搜索安装idea插件,然后,在设置里,点击other settings,添加我们的checkstyle 17 | 规则文件。 18 | 19 | 之后可以在当前java文件上邮件,check current file查看检查结果. 20 | 21 | 22 | 23 | ## idea format自动格式化 24 | 25 | idea自带的格式化需要根据我们的checkstyle配置文件进行修改, 26 | ``` 27 | checkstyle\intellij-java-google-style.xml 28 | ``` 29 | 30 | 上述就是idea对应的配置文件,导入方式为,settings-editor-code style 31 | - scheme - 点击右边的设置按钮 - import idea code style xml. 32 | 33 | 34 | 35 | 36 | ## 提交前检查 37 | 38 | 项目提交到git之前,需要本地在根目录运行 39 | ``` 40 | mvn clean site 41 | mvn install 42 | ``` 43 | 失败则检查失败原因,如果是checkstyle检查不过,修改格式. 44 | 45 | 46 | ## 报告位置 47 | 48 | 失败后会生成一个xml结果文件,比如 49 | 50 | ``` 51 | target/site/checkstyle.html 52 | ``` 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /config/checkstyle/google-checks-8.29.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 65 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 82 | 83 | 84 | 86 | 87 | 88 | 94 | 95 | 96 | 97 | 100 | 101 | 102 | 103 | 104 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 122 | 124 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 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 | 171 | 172 | 173 | 174 | 176 | 177 | 178 | 179 | 181 | 182 | 183 | 184 | 186 | 187 | 188 | 189 | 191 | 192 | 193 | 194 | 196 | 197 | 198 | 199 | 201 | 202 | 203 | 204 | 206 | 207 | 208 | 209 | 211 | 212 | 213 | 214 | 216 | 217 | 218 | 219 | 221 | 223 | 225 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 255 | 256 | 257 | 259 | 260 | 261 | 262 | 267 | 268 | 269 | 270 | 273 | 274 | 275 | 276 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 328 | 329 | 330 | 331 | 332 | -------------------------------------------------------------------------------- /config/checkstyle/intellij-java-google-style.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 19 | 25 | 32 | 625 | -------------------------------------------------------------------------------- /config/findbugs/findbugs-exclude.xml: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # https://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Maven2 Start Up Batch script 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # M2_HOME - location of maven2's installed home dir 31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 32 | # e.g. to debug Maven itself, use 33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 35 | # ---------------------------------------------------------------------------- 36 | 37 | if [ -z "$MAVEN_SKIP_RC" ] ; then 38 | 39 | if [ -f /etc/mavenrc ] ; then 40 | . /etc/mavenrc 41 | fi 42 | 43 | if [ -f "$HOME/.mavenrc" ] ; then 44 | . "$HOME/.mavenrc" 45 | fi 46 | 47 | fi 48 | 49 | # OS specific support. $var _must_ be set to either true or false. 50 | cygwin=false; 51 | darwin=false; 52 | mingw=false 53 | case "`uname`" in 54 | CYGWIN*) cygwin=true ;; 55 | MINGW*) mingw=true;; 56 | Darwin*) darwin=true 57 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 58 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 59 | if [ -z "$JAVA_HOME" ]; then 60 | if [ -x "/usr/libexec/java_home" ]; then 61 | export JAVA_HOME="`/usr/libexec/java_home`" 62 | else 63 | export JAVA_HOME="/Library/Java/Home" 64 | fi 65 | fi 66 | ;; 67 | esac 68 | 69 | if [ -z "$JAVA_HOME" ] ; then 70 | if [ -r /etc/gentoo-release ] ; then 71 | JAVA_HOME=`java-config --jre-home` 72 | fi 73 | fi 74 | 75 | if [ -z "$M2_HOME" ] ; then 76 | ## resolve links - $0 may be a link to maven's home 77 | PRG="$0" 78 | 79 | # need this for relative symlinks 80 | while [ -h "$PRG" ] ; do 81 | ls=`ls -ld "$PRG"` 82 | link=`expr "$ls" : '.*-> \(.*\)$'` 83 | if expr "$link" : '/.*' > /dev/null; then 84 | PRG="$link" 85 | else 86 | PRG="`dirname "$PRG"`/$link" 87 | fi 88 | done 89 | 90 | saveddir=`pwd` 91 | 92 | M2_HOME=`dirname "$PRG"`/.. 93 | 94 | # make it fully qualified 95 | M2_HOME=`cd "$M2_HOME" && pwd` 96 | 97 | cd "$saveddir" 98 | # echo Using m2 at $M2_HOME 99 | fi 100 | 101 | # For Cygwin, ensure paths are in UNIX format before anything is touched 102 | if $cygwin ; then 103 | [ -n "$M2_HOME" ] && 104 | M2_HOME=`cygpath --unix "$M2_HOME"` 105 | [ -n "$JAVA_HOME" ] && 106 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 107 | [ -n "$CLASSPATH" ] && 108 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"` 109 | fi 110 | 111 | # For Mingw, ensure paths are in UNIX format before anything is touched 112 | if $mingw ; then 113 | [ -n "$M2_HOME" ] && 114 | M2_HOME="`(cd "$M2_HOME"; pwd)`" 115 | [ -n "$JAVA_HOME" ] && 116 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" 117 | # TODO classpath? 118 | fi 119 | 120 | if [ -z "$JAVA_HOME" ]; then 121 | javaExecutable="`which javac`" 122 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then 123 | # readlink(1) is not available as standard on Solaris 10. 124 | readLink=`which readlink` 125 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then 126 | if $darwin ; then 127 | javaHome="`dirname \"$javaExecutable\"`" 128 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" 129 | else 130 | javaExecutable="`readlink -f \"$javaExecutable\"`" 131 | fi 132 | javaHome="`dirname \"$javaExecutable\"`" 133 | javaHome=`expr "$javaHome" : '\(.*\)/bin'` 134 | JAVA_HOME="$javaHome" 135 | export JAVA_HOME 136 | fi 137 | fi 138 | fi 139 | 140 | if [ -z "$JAVACMD" ] ; then 141 | if [ -n "$JAVA_HOME" ] ; then 142 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 143 | # IBM's JDK on AIX uses strange locations for the executables 144 | JAVACMD="$JAVA_HOME/jre/sh/java" 145 | else 146 | JAVACMD="$JAVA_HOME/bin/java" 147 | fi 148 | else 149 | JAVACMD="`which java`" 150 | fi 151 | fi 152 | 153 | if [ ! -x "$JAVACMD" ] ; then 154 | echo "Error: JAVA_HOME is not defined correctly." >&2 155 | echo " We cannot execute $JAVACMD" >&2 156 | exit 1 157 | fi 158 | 159 | if [ -z "$JAVA_HOME" ] ; then 160 | echo "Warning: JAVA_HOME environment variable is not set." 161 | fi 162 | 163 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher 164 | 165 | # traverses directory structure from process work directory to filesystem root 166 | # first directory with .mvn subdirectory is considered project base directory 167 | find_maven_basedir() { 168 | 169 | if [ -z "$1" ] 170 | then 171 | echo "Path not specified to find_maven_basedir" 172 | return 1 173 | fi 174 | 175 | basedir="$1" 176 | wdir="$1" 177 | while [ "$wdir" != '/' ] ; do 178 | if [ -d "$wdir"/.mvn ] ; then 179 | basedir=$wdir 180 | break 181 | fi 182 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 183 | if [ -d "${wdir}" ]; then 184 | wdir=`cd "$wdir/.."; pwd` 185 | fi 186 | # end of workaround 187 | done 188 | echo "${basedir}" 189 | } 190 | 191 | # concatenates all lines of a file 192 | concat_lines() { 193 | if [ -f "$1" ]; then 194 | echo "$(tr -s '\n' ' ' < "$1")" 195 | fi 196 | } 197 | 198 | BASE_DIR=`find_maven_basedir "$(pwd)"` 199 | if [ -z "$BASE_DIR" ]; then 200 | exit 1; 201 | fi 202 | 203 | ########################################################################################## 204 | # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 205 | # This allows using the maven wrapper in projects that prohibit checking in binary data. 206 | ########################################################################################## 207 | if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then 208 | if [ "$MVNW_VERBOSE" = true ]; then 209 | echo "Found .mvn/wrapper/maven-wrapper.jar" 210 | fi 211 | else 212 | if [ "$MVNW_VERBOSE" = true ]; then 213 | echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." 214 | fi 215 | jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar" 216 | while IFS="=" read key value; do 217 | case "$key" in (wrapperUrl) jarUrl="$value"; break ;; 218 | esac 219 | done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" 220 | if [ "$MVNW_VERBOSE" = true ]; then 221 | echo "Downloading from: $jarUrl" 222 | fi 223 | wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" 224 | 225 | if command -v wget > /dev/null; then 226 | if [ "$MVNW_VERBOSE" = true ]; then 227 | echo "Found wget ... using wget" 228 | fi 229 | wget "$jarUrl" -O "$wrapperJarPath" 230 | elif command -v curl > /dev/null; then 231 | if [ "$MVNW_VERBOSE" = true ]; then 232 | echo "Found curl ... using curl" 233 | fi 234 | curl -o "$wrapperJarPath" "$jarUrl" 235 | else 236 | if [ "$MVNW_VERBOSE" = true ]; then 237 | echo "Falling back to using Java to download" 238 | fi 239 | javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" 240 | if [ -e "$javaClass" ]; then 241 | if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 242 | if [ "$MVNW_VERBOSE" = true ]; then 243 | echo " - Compiling MavenWrapperDownloader.java ..." 244 | fi 245 | # Compiling the Java class 246 | ("$JAVA_HOME/bin/javac" "$javaClass") 247 | fi 248 | if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 249 | # Running the downloader 250 | if [ "$MVNW_VERBOSE" = true ]; then 251 | echo " - Running MavenWrapperDownloader.java ..." 252 | fi 253 | ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") 254 | fi 255 | fi 256 | fi 257 | fi 258 | ########################################################################################## 259 | # End of extension 260 | ########################################################################################## 261 | 262 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} 263 | if [ "$MVNW_VERBOSE" = true ]; then 264 | echo $MAVEN_PROJECTBASEDIR 265 | fi 266 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 267 | 268 | # For Cygwin, switch paths to Windows format before running java 269 | if $cygwin; then 270 | [ -n "$M2_HOME" ] && 271 | M2_HOME=`cygpath --path --windows "$M2_HOME"` 272 | [ -n "$JAVA_HOME" ] && 273 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` 274 | [ -n "$CLASSPATH" ] && 275 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"` 276 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 277 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` 278 | fi 279 | 280 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 281 | 282 | exec "$JAVACMD" \ 283 | $MAVEN_OPTS \ 284 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 285 | "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 286 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 287 | -------------------------------------------------------------------------------- /mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM https://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven2 Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM set title of command window 39 | title %0 40 | @REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' 41 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 42 | 43 | @REM set %HOME% to equivalent of $HOME 44 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 45 | 46 | @REM Execute a user defined script before this one 47 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 48 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 49 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" 50 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" 51 | :skipRcPre 52 | 53 | @setlocal 54 | 55 | set ERROR_CODE=0 56 | 57 | @REM To isolate internal variables from possible post scripts, we use another setlocal 58 | @setlocal 59 | 60 | @REM ==== START VALIDATION ==== 61 | if not "%JAVA_HOME%" == "" goto OkJHome 62 | 63 | echo. 64 | echo Error: JAVA_HOME not found in your environment. >&2 65 | echo Please set the JAVA_HOME variable in your environment to match the >&2 66 | echo location of your Java installation. >&2 67 | echo. 68 | goto error 69 | 70 | :OkJHome 71 | if exist "%JAVA_HOME%\bin\java.exe" goto init 72 | 73 | echo. 74 | echo Error: JAVA_HOME is set to an invalid directory. >&2 75 | echo JAVA_HOME = "%JAVA_HOME%" >&2 76 | echo Please set the JAVA_HOME variable in your environment to match the >&2 77 | echo location of your Java installation. >&2 78 | echo. 79 | goto error 80 | 81 | @REM ==== END VALIDATION ==== 82 | 83 | :init 84 | 85 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 86 | @REM Fallback to current working directory if not found. 87 | 88 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 89 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 90 | 91 | set EXEC_DIR=%CD% 92 | set WDIR=%EXEC_DIR% 93 | :findBaseDir 94 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 95 | cd .. 96 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 97 | set WDIR=%CD% 98 | goto findBaseDir 99 | 100 | :baseDirFound 101 | set MAVEN_PROJECTBASEDIR=%WDIR% 102 | cd "%EXEC_DIR%" 103 | goto endDetectBaseDir 104 | 105 | :baseDirNotFound 106 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 107 | cd "%EXEC_DIR%" 108 | 109 | :endDetectBaseDir 110 | 111 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 112 | 113 | @setlocal EnableExtensions EnableDelayedExpansion 114 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 115 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 116 | 117 | :endReadAdditionalConfig 118 | 119 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 120 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 121 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 122 | 123 | set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar" 124 | FOR /F "tokens=1,2 delims==" %%A IN (%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties) DO ( 125 | IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B 126 | ) 127 | 128 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 129 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data. 130 | if exist %WRAPPER_JAR% ( 131 | echo Found %WRAPPER_JAR% 132 | ) else ( 133 | echo Couldn't find %WRAPPER_JAR%, downloading it ... 134 | echo Downloading from: %DOWNLOAD_URL% 135 | powershell -Command "(New-Object Net.WebClient).DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')" 136 | echo Finished downloading %WRAPPER_JAR% 137 | ) 138 | @REM End of extension 139 | 140 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 141 | if ERRORLEVEL 1 goto error 142 | goto end 143 | 144 | :error 145 | set ERROR_CODE=1 146 | 147 | :end 148 | @endlocal & set ERROR_CODE=%ERROR_CODE% 149 | 150 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost 151 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 152 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" 153 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" 154 | :skipRcPost 155 | 156 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 157 | if "%MAVEN_BATCH_PAUSE%" == "on" pause 158 | 159 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% 160 | 161 | exit /B %ERROR_CODE% 162 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.example 8 | code-quality-verify-demo 9 | 0.0.1-SNAPSHOT 10 | code-quality-verify-demo 11 | Demo project for Spring Boot 12 | 13 | 14 | 1.8 15 | 2.1.3.RELEASE 16 | UTF-8 17 | UTF-8 18 | 0.00 19 | 0.00 20 | 21 | 22 | 23 | 24 | 25 | central 26 | http://maven.aliyun.com/nexus/content/groups/public/ 27 | aliyun 28 | 29 | 30 | 31 | maven-central 32 | http://repo1.maven.org/maven2/ 33 | maven 34 | 35 | 36 | 37 | jitpack.io 38 | https://jitpack.io 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | org.springframework.boot 47 | spring-boot-dependencies 48 | 2.1.5.RELEASE 49 | pom 50 | import 51 | 52 | 53 | 54 | 55 | 56 | 57 | org.springframework.boot 58 | spring-boot-starter-web 59 | 60 | 61 | 62 | org.springframework.boot 63 | spring-boot-starter-test 64 | test 65 | 66 | 67 | 68 | 69 | ${project.artifactId} 70 | 71 | 72 | maven-compiler-plugin 73 | 3.6.0 74 | 75 | ${java.version} 76 | ${java.version} 77 | UTF-8 78 | 79 | 80 | 81 | org.springframework.boot 82 | spring-boot-maven-plugin 83 | ${springboot.version} 84 | 85 | 86 | 87 | build-info 88 | repackage 89 | 90 | 91 | 92 | 93 | 94 | org.apache.maven.plugins 95 | maven-surefire-plugin 96 | 3.0.0-M3 97 | 98 | 99 | 100 | 101 | org.apache.maven.plugins 102 | maven-pmd-plugin 103 | 3.8 104 | 105 | ${project.build.sourceEncoding} 106 | ${java.version} 107 | true 108 | 109 | 110 | rulesets/java/ali-comment.xml 111 | rulesets/java/ali-concurrent.xml 112 | rulesets/java/ali-constant.xml 113 | rulesets/java/ali-exception.xml 114 | rulesets/java/ali-flowcontrol.xml 115 | rulesets/java/ali-naming.xml 116 | rulesets/java/ali-oop.xml 117 | rulesets/java/ali-orm.xml 118 | rulesets/java/ali-other.xml 119 | rulesets/java/ali-set.xml 120 | 121 | 122 | 123 | **/*Bean.java 124 | **/generated/**.java 125 | 126 | true 127 | 5 128 | 129 | 130 | 131 | com.alibaba.p3c 132 | p3c-pmd 133 | 1.3.6 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | org.apache.maven.plugins 157 | maven-checkstyle-plugin 158 | 3.1.1 159 | 160 | config/checkstyle/google-checks-8.29.xml 161 | true 162 | UTF-8 163 | true 164 | false 165 | false 166 | true 167 | error 168 | 169 | **/generated/**.java 170 | 171 | 172 | 173 | 174 | 175 | org.apache.maven.plugins 176 | maven-site-plugin 177 | 3.7.1 178 | 179 | 180 | 181 | org.codehaus.mojo 182 | findbugs-maven-plugin 183 | 3.0.5 184 | 185 | true 186 | 187 | target/site 188 | 189 | 190 | 191 | 192 | config/findbugs/findbugs-exclude.xml 193 | Min 194 | High 195 | true 196 | 197 | 198 | 199 | org.jacoco 200 | jacoco-maven-plugin 201 | 0.8.4 202 | 203 | 204 | **/exceptions/**/* 205 | **/vo/**/* 206 | **/exception/**/* 207 | **/entity/**/* 208 | **/constant/* 209 | **/*Test.* 210 | 211 | 212 | 213 | 214 | jacoco-initialize 215 | 216 | prepare-agent 217 | 218 | 219 | 220 | report 221 | verify 222 | 223 | report 224 | 225 | 226 | 227 | jacoco-check 228 | install 229 | 230 | check 231 | 232 | 233 | 234 | 235 | BUNDLE 236 | 237 | 238 | INSTRUCTION 239 | COVEREDRATIO 240 | ${jacoco.percentage.instruction} 241 | 242 | 243 | BRANCH 244 | COVEREDRATIO 245 | ${jacoco.percentage.branch} 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | org.apache.maven.plugins 262 | maven-jxr-plugin 263 | 2.3 264 | 265 | 266 | org.apache.maven.plugins 267 | maven-pmd-plugin 268 | 3.8 269 | 270 | ${project.build.sourceEncoding} 271 | ${java.version} 272 | true 273 | 274 | 275 | rulesets/java/ali-comment.xml 276 | rulesets/java/ali-concurrent.xml 277 | rulesets/java/ali-constant.xml 278 | rulesets/java/ali-exception.xml 279 | rulesets/java/ali-flowcontrol.xml 280 | rulesets/java/ali-naming.xml 281 | rulesets/java/ali-oop.xml 282 | rulesets/java/ali-orm.xml 283 | rulesets/java/ali-other.xml 284 | rulesets/java/ali-set.xml 285 | 286 | 287 | 288 | **/*Bean.java 289 | **/generated/**.java 290 | 291 | 292 | 293 | 294 | 295 | org.apache.maven.plugins 296 | maven-checkstyle-plugin 297 | 3.0.0 298 | 299 | config/checkstyle/google-checks-6.18.xml 300 | true 301 | UTF-8 302 | true 303 | false 304 | true 305 | error 306 | 307 | **/generated/**.java 308 | 309 | 310 | 311 | 312 | checkstyle 313 | 314 | 315 | 316 | 317 | 318 | org.codehaus.mojo 319 | findbugs-maven-plugin 320 | 3.0.5 321 | 322 | true 323 | 324 | target/site 325 | 326 | 327 | 328 | 329 | config/findbugs/findbugs-exclude.xml 330 | 331 | 332 | 333 | org.apache.maven.plugins 334 | maven-project-info-reports-plugin 335 | 3.0.0 336 | 337 | 338 | 339 | true 340 | false 341 | 342 | 343 | 344 | 345 | 346 | org.apache.maven.plugins 347 | maven-surefire-report-plugin 348 | 3.0.0-M3 349 | 350 | 351 | 352 | 353 | 354 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | 质量扫描demo 2 | =========== 3 | 4 | 5 | 6 | 7 | demo项目展示如何集成各种Java项目的质量扫描。目前支持 8 | 9 | - checkstyle 10 | - pmd/p3c 11 | - cpd 12 | - findbugs 13 | - jacoco 14 | - sonarqube 15 | 16 | ## Jenkins配置 17 | 18 | 多分支扫描 / 19 | 20 | 21 | ## 开发流程 22 | 23 | 24 | ## 检测报告 25 | 26 | 27 | ## 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /sonar-project.properties: -------------------------------------------------------------------------------- 1 | # must be unique in a given SonarQube instance 2 | sonar.projectKey=bigdata:Demo:code-quality-verify-demo 3 | # this is the name and version displayed in the SonarQube UI. Was mandatory prior to SonarQube 6.1. 4 | sonar.projectName=Demo/code-quality-verify-demo 5 | sonar.projectVersion=1.0 6 | 7 | # Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows. 8 | # This property is optional if sonar.modules is set. 9 | 10 | 11 | sonar.sources=src 12 | sonar.projectBaseDir=. 13 | 14 | # Encoding of the source code. Default is default system encoding 15 | #sonar.sourceEncoding=UTF-8 16 | sonar.java.binaries=./target/ 17 | sonar.java.source=1.8 18 | -------------------------------------------------------------------------------- /src/main/java/com/example/codequalityverifydemo/CodeQualityVerifyDemoApplication.java: -------------------------------------------------------------------------------- 1 | package com.example.codequalityverifydemo; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class CodeQualityVerifyDemoApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(CodeQualityVerifyDemoApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/example/codequalityverifydemo/TestController.java: -------------------------------------------------------------------------------- 1 | package com.example.codequalityverifydemo; 2 | 3 | import com.example.codequalityverifydemo.domain.service.ITestService; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.web.bind.annotation.GetMapping; 6 | import org.springframework.web.bind.annotation.PathVariable; 7 | import org.springframework.web.bind.annotation.RestController; 8 | 9 | @RestController 10 | public class TestController { 11 | 12 | private final ITestService testService; 13 | 14 | @Autowired 15 | public TestController(ITestService testService) { 16 | this.testService = testService; 17 | } 18 | 19 | @GetMapping("/say/{msg}") 20 | public String say(@PathVariable String msg){ 21 | 22 | return testService.print(msg); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/example/codequalityverifydemo/TestController2.java: -------------------------------------------------------------------------------- 1 | package com.example.codequalityverifydemo; 2 | 3 | import com.example.codequalityverifydemo.domain.service.ITestService; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.web.bind.annotation.GetMapping; 6 | import org.springframework.web.bind.annotation.PathVariable; 7 | import org.springframework.web.bind.annotation.RestController; 8 | 9 | /** 10 | * @author Ryan 11 | */ 12 | @RestController 13 | public class TestController2 { 14 | 15 | private final ITestService testService; 16 | 17 | @Autowired 18 | public TestController2(ITestService testService) { 19 | this.testService = testService; 20 | } 21 | 22 | @GetMapping("/say2/{msg}") 23 | public String say(@PathVariable String msg){ 24 | 25 | return testService.print(msg); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/example/codequalityverifydemo/domain/service/ITestService.java: -------------------------------------------------------------------------------- 1 | package com.example.codequalityverifydemo.domain.service; 2 | 3 | public interface ITestService { 4 | 5 | String print(String msg); 6 | 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/example/codequalityverifydemo/domain/service/impl/TestService.java: -------------------------------------------------------------------------------- 1 | package com.example.codequalityverifydemo.domain.service.impl; 2 | 3 | import com.example.codequalityverifydemo.domain.service.ITestService; 4 | import org.springframework.stereotype.Service; 5 | 6 | @Service 7 | public class TestService implements ITestService { 8 | 9 | private Integer age; 10 | 11 | public String print(String msg) { 12 | System.out.println(msg); 13 | if (age<0 || age > 100){ 14 | System.out.println("年龄写错啦"); 15 | } 16 | return msg; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/test/java/com/example/codequalityverifydemo/CodeQualityVerifyDemoApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.example.codequalityverifydemo; 2 | 3 | import org.junit.Test; 4 | 5 | public class CodeQualityVerifyDemoApplicationTests { 6 | 7 | @Test 8 | public void contextLoads() { 9 | System.out.println("This is the Unit Test"); 10 | } 11 | 12 | } 13 | --------------------------------------------------------------------------------