赞
踩
原理说明:首先使用DependencyCheck更新漏洞库到本地,然后DependencyCheck扫描扫描项目,得到一个json报告,然后再使用json报告填充我们的自定义模板,最后输出填充后的模板为漏洞报告。至于jenkins只是最后帮我们实现全自动扫描、输出的一个工具而已。
官方网站:OWASP Dependency-Check | OWASP Foundation
开箱即用:
将zip压缩包解压即可,我就放这里了:/root/dependency-check。执行bin目录下的dependency-check.bat或者dependency-check.sh。
命令如下:
- dependency-check.sh --project "第三方依赖CVE漏洞扫描报告" --scan "**/*.jar" -n -f JSON -o "第三方依赖CVE漏洞扫描报告.json"
-
- --project 给这次扫描的项目起个名称
- --scan 要扫描的文件路径
- -n 此次扫描不更新漏洞库:不加这个会先去更新漏洞库,然后再进行扫描
- -f 要输出的报告格式,有HTML ,JSON等
- -o 扫描报告的存放路径
这里要使用大佬的开源工具:nist-data-mirror.jar。开源地址:stevespringett/nist-data-mirror: A simple Java command-line utility to mirror the CVE JSON data from NIST. (github.com)
为了方便管理我把这个工具放在这里:/root/dependency-check/data/nist-data-mirror.jar。加载nvd漏洞库(第一次有点慢,从2000年开始的):
java -jar /root/dependency-check/data/nist-data-mirror.jar /root/dependency-check/data/nvdcache
将更新下来的JSON数据提取,保存在数据库表里面,核心代码:
- package com.yunhuang.autosafe.common.service.impl;
-
- import cn.hutool.core.date.DateUtil;
- import cn.hutool.core.io.resource.ClassPathResource;
- import cn.hutool.json.JSONObject;
- import cn.hutool.json.JSONUtil;
- import com.alibaba.fastjson.JSON;
- import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
- import com.yunhuang.autosafe.common.cve.CveObj;
- import com.yunhuang.autosafe.common.entity.Cve;
- import com.yunhuang.autosafe.common.mapper.CveMapper;
- import com.yunhuang.autosafe.common.service.CveService;
- import org.apache.commons.io.FileUtils;
- import org.springframework.stereotype.Service;
-
- import java.io.*;
- import java.nio.file.Files;
- import java.nio.file.Paths;
- import java.util.ArrayList;
- import java.util.List;
-
-
- @Service
- public class CveServiceImpl extends ServiceImpl<CveMapper, Cve> implements CveService {
-
- /**
- * 拉去cve漏洞数据库
- * @param loadType 0-全量初始化 1-增量更新
- */
- @Override
- public void loadCveData(Integer loadType) {
- String workspace = System.getProperty("user.dir");
- String nvdcachePath = workspace + "/nvdcache";
- System.out.println("工作空间="+workspace);
- //1、拉取NVD的cve漏洞文件
- syncCveList(workspace,nvdcachePath);
-
- //2、将拉取到的json文件进行解析并提取为对象,转存mysql
- if(0==loadType){
- readFilesAll(nvdcachePath);
- }else if(1==loadType){
- updateRecentCve(nvdcachePath);
- }
- }
-
- /**
- * 拉取NVD的cve漏洞文件(json文件)
- * @param workspace 工作空间:默认当前jar的路径
- * @param nvdcachePath 存储漏洞库文件(json文件)的路径
- */
- public void syncCveList(String workspace,String nvdcachePath) {
- //1、检查缓存目录是否存在
- File nvdcacheFile = new File(nvdcachePath);
- if(!nvdcacheFile.exists()){
- System.out.println("缓存目录不存在,创建!");
- nvdcacheFile.mkdirs();
- }
-
- String nistDataMirror = workspace + "/nist-data-mirror.jar";
- File nistDataMirrorFile = new File(nistDataMirror);
- if(!nistDataMirrorFile.exists()){
- System.out.println("nist-data-mirror.jar不存在,创建!");
- try{
- ClassPathResource classPathResource = new ClassPathResource("utils/nist-data-mirror.jar");
- InputStream inputStream = classPathResource.getStream();
- String destPath = workspace + "/nist-data-mirror.jar";
-
- FileUtils.copyInputStreamToFile(inputStream, new File(destPath));
-
- }catch (Exception e){
- e.printStackTrace();
- }
- }
-
-
- //1、拉取NVD的cve漏洞文件
- String command = "java -jar "+workspace+"/nist-data-mirror.jar "+nvdcachePath;
-
- try {
- Process process = Runtime.getRuntime().exec(command);
-
- // 读取命令执行的输出
- InputStream inputStream = process.getInputStream();
- BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
-
- String line;
- while ((line = reader.readLine()) != null) {
- System.out.println(line);
- }
-
- // 等待命令执行结束
- int exitCode = process.waitFor();
- System.out.println("命令执行结束,退出码:" + exitCode);
-
- } catch (IOException | InterruptedException e) {
- e.printStackTrace();
- }
- }
-
-
-
-
-
- private void readFilesAll(String nvdcachePath){
- System.out.println("------------全量初始化------------");
- File nvdcacheFile = new File(nvdcachePath);
- File[] files = nvdcacheFile.listFiles((dir, name) -> name.toLowerCase().endsWith(".json"));
- for(File f : files){
- System.out.println(f.getAbsolutePath());
- parseJson(f.getAbsolutePath());
- System.out.println("");
- }
- System.out.println("------------全量初始化-完成------------");
- }
-
- private void updateRecentCve(String nvdcachePath){
- System.out.println("------------增量更新------------");
- File nvdcacheFile = new File(nvdcachePath);
- File[] files = nvdcacheFile.listFiles((dir, name) -> name.toLowerCase().endsWith(DateUtil.thisYear()+".json") || name.toLowerCase().endsWith("modified.json") || name.toLowerCase().endsWith("recent.json"));
- for(File f : files){
- System.out.println(f.getAbsolutePath());
- parseJson(f.getAbsolutePath());
- System.out.println("");
- }
- System.out.println("------------增量更新-完成------------");
- }
-
-
- private void parseJson(String filePath){
- try {
- String jsonStr = new String( Files.readAllBytes( Paths.get(filePath)));
- JSONObject jsonObject = JSONUtil.parseObj(jsonStr);
- String CVE_Items = jsonObject.getStr("CVE_Items");
- List<CveObj> cveObjs = JSONUtil.toList(CVE_Items, CveObj.class);
-
- List<Cve> list = new ArrayList<>();
- // 对解析后的对象数组进行操作
- for (CveObj obj : cveObjs) {
- Cve cve = new Cve();
- cve.setId(obj.getCve().getCVE_data_meta().getID());
- try{
- cve.setSeverity(obj.getImpact().getBaseMetricV2().getSeverity()+"");
- }catch (Exception e){
-
- }
- cve.setDescription(JSON.toJSONString(obj.getCve().getDescription().getDescription_data()).replaceAll("'",""));
- cve.setPublishedDate(DateUtil.parseDate(obj.getPublishedDate()));
- cve.setLastModifiedDate(DateUtil.parseDate(obj.getLastModifiedDate()));
-
- list.add(cve);
- }
-
- this.saveOrUpdateBatch(list);
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
CNNVD是我们国家的信息安全漏洞库,咱们这个漏洞库不仅仅收录了NVD的漏洞数据(CVE),还收录咱们国内的漏洞信息(CNNVD),也就是说同样一条漏洞信息,CNNVD的漏洞数据上面既有CVE漏洞编码,也有CNNVD编码,而且描述信息还是中文的。
获取方式:
CNNVD漏洞数据库下载后是xml格式的文件,我们需要写代码将xml里面我们需要的数据提取出来,保存在数据库表里面
- @GetMapping
- public void insertCnnvd(int type) throws JAXBException {
-
-
- if(type==0){
- for(int i=1999;i<=2023;i++){
- System.out.println("-------->开始读取"+i+"年数据:");
- List<Cnnvd> nvdList = parseXml(i);
- System.out.println("-------->成功读取"+i+"年数据:"+nvdList.size());
- cnnvdService.saveOrUpdateBatch(nvdList);
- System.out.println("-------->成功写入数据:"+nvdList.size());
- System.out.println("");
- System.out.println("");
- }
- }else{
- System.out.println("-------->开始读取"+DateUtil.thisYear()+"年数据:");
- List<Cnnvd> cnnvdList = parseXml(2023);
- System.out.println("-------->成功读取"+DateUtil.thisYear()+"年数据:"+cnnvdList.size());
- cnnvdService.saveOrUpdateBatch(cnnvdList);
- System.out.println("-------->成功写入数据:"+cnnvdList.size());
- }
- }
-
- public List<Cnnvd> parseXml(int fileName) throws JAXBException {
- //可能的问题:[org.xml.sax.SAXParseException; lineNumber: 2292; columnNumber: 191; 元素类型 "use" 必须由匹配的结束标记 "</use>" 终止。]] with root cause
- //解决的方案:在内网的签名添加字符串:<![CDATA[ 在内网的结尾添加字符串:]]>
- InputStream stream = this.getClass().getClassLoader().getResourceAsStream("cnnvd/"+fileName+".xml");
-
- JAXBContext jaxbContext = JAXBContext.newInstance(VulnerabilityList.class);
- Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
- VulnerabilityList vulnerabilityList = (VulnerabilityList) jaxbUnmarshaller.unmarshal(stream);
- List<Cnnvd> vulnerabilities = vulnerabilityList.vulnerabilities;
-
- return vulnerabilities;
- }
- @Data
- @XmlRootElement(name = "entry")
- @XmlAccessorType(XmlAccessType.FIELD)
- public class Cnnvd implements Serializable {
- @TableId(type = IdType.INPUT)
- @XmlElement(name = "vuln-id")
- public String id;
- @XmlElement(name = "name")
- public String name;
- @XmlElement(name = "published")
- public String published;
- @XmlElement(name = "modified")
- public String modified;
- @XmlElement(name = "source")
- public String source;
- @XmlElement(name = "severity")
- public String severity;
- @XmlElement(name = "vuln-type")
- public String vulnType;
- @XmlElement(name = "vuln-descript")
- public String description;
- @XmlElement(name = "cve-id")
- public String cveId;
- @XmlElement(name = "bugtraq-id")
- public String bugtraqId;
- @XmlElement(name = "vuln-solution")
- public String solution;
- }
/root/dependency-check/bin/dependency-check.sh --project "test-服务端第三方依赖CVE漏洞扫描报告" --scan "**/*.jar" -n -f JSON -o "/var/www/html/test-服务端第三方依赖CVE漏洞扫描报告.json"
将上面扫描的“test-服务端第三方依赖CVE漏洞扫描报告.json”数据解析出来,填充到我们的自定义模板中,最终生成我们的word报告。
- /**
- * 根据json报告解析漏洞信息并入库:方便后续直接构建word和excel
- * @param name 报告名称
- * @param sourePath json报告路径
- * @param reportPath word报告路径
- * @param gitHttpUrl git的http地址
- * @param gitBranch git分支
- * @return
- * @throws IOException
- */
- @GetMapping
- public String vulScanReport(String name, String sourePath,String reportPath,String gitHttpUrl,String gitBranch) throws IOException {
- //解析扫描的json报告,并将数据写入数据库
- int num = cnnvdService.analysisCveJsonv2(name,sourePath, gitHttpUrl, gitBranch);
- if(num>0){
- //读取扫描的漏洞数据,填充自定义模板,生成word报告
- String fileName = cnnvdService.createWord(name,reportPath,gitHttpUrl,gitBranch);
- return fileName;
- }else{
- return "";
- }
- }
- package com.yunhuang.autosafe.common.service.impl;
-
-
- import cn.hutool.core.date.DateUtil;
- import cn.hutool.json.JSONArray;
- import cn.hutool.json.JSONObject;
- import cn.hutool.json.JSONUtil;
- import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
- import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
- import com.deepoove.poi.XWPFTemplate;
- import com.deepoove.poi.config.Configure;
- import com.deepoove.poi.plugin.table.LoopRowTableRenderPolicy;
- import com.deepoove.poi.util.PoitlIOUtils;
- import com.yunhuang.autosafe.common.entity.*;
- import com.yunhuang.autosafe.common.mapper.CnnvdMapper;
- import com.yunhuang.autosafe.common.service.*;
- import lombok.Data;
- import org.apache.commons.lang3.StringUtils;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.data.redis.core.RedisTemplate;
- import org.springframework.stereotype.Service;
- import javax.annotation.Resource;
- import java.io.*;
- import java.util.*;
- import java.util.function.Function;
- import java.util.stream.Collectors;
-
-
- @Service
- public class CnnvdServiceImpl extends ServiceImpl<CnnvdMapper, Cnnvd> implements CnnvdService {
-
- @Autowired
- private NvdMyService nvdMyService;
- @Autowired
- private CveService cveService;
- @Autowired
- private ScanDependencieService dependencieService;
- @Autowired
- private ScanVulnerabilitieService vulnerabilitieService;
- @Resource
- private RedisTemplate redisTemplate;
-
- private final static String REDIS_NVD_KEY = "cve_data";
-
- /**
- * 根据json报告解析漏洞信息并入库:方便后续直接构建word和excel
- * @param name 报告名称
- * @param sourePath json报告路径
- * @param gitHttpUrl git的http地址
- * @param gitBranch git分支
- * @return
- * @throws IOException
- */
- @Override
- public Integer analysisCveJsonv2(String name, String sourePath, String gitHttpUrl, String gitBranch) throws IOException {
- //数据库统一记录时间
- Date createTime = new Date();
- String jsonStr = readFileContent(sourePath);
- JSONObject jsonObject = JSONUtil.parseObj(jsonStr);
-
- //依赖
- String dependencies = jsonObject.getStr("dependencies");
- JSONArray array = JSONUtil.parseArray(dependencies);
-
- //依赖下的漏洞
- List<ScanDependencie> dependencieList = JSONUtil.toList(array, ScanDependencie.class);
- System.out.println("--------------->依赖项总数:"+dependencieList.size());
-
- //漏洞详情列表
- List<ScanDependencie> dependencieVulList = new ArrayList<>();
- //漏洞清单汇总
- List<ScanVulnerabilitie> vulnerabilitiesAll = new ArrayList<>();
- //扫描到的漏洞清单
- List<String> cveScanList = new ArrayList<>();
-
-
- int i = 1;
-
- //遍历依赖:构建导出数据
- for(ScanDependencie d :dependencieList){
- if(d.getVulnerabilities()==null || d.getVulnerabilities().size()==0) continue;
-
- int pindex = i++;
- String depId = UUID.randomUUID().toString().replaceAll("-","");
- d.setId(depId);
- d.setName(name);
- d.setSortIndex(pindex);
- d.setFileName(d.getFileName().replaceAll(" ",""));
- d.setGitHttpUrl(gitHttpUrl);
- d.setGitBranch(gitBranch);
- d.setScanDate(createTime);
- if(StringUtils.isNotBlank(d.getDescription())) d.setDescription(d.getDescription().trim());
-
- int m = 1;
- //遍历依赖下的漏洞
- List<ScanVulnerabilitie> vulnerabilities = d.getVulnerabilities();
- for(ScanVulnerabilitie vul : vulnerabilities){
- if(vul.getName().indexOf("CVE")==-1) continue;
-
- //添加到扫描漏洞清单
- cveScanList.add(vul.getName());
-
- int index = m++;
- vul.setDependencieId(depId);
- vul.setSortIndex(index);
- vul.setSortIndexStr(pindex+"."+index);
- vul.setScanDate(createTime);
-
- vulnerabilitiesAll.add(vul);
- }
-
- dependencieVulList.add(d);
- }
-
-
- //清除之前的数据
- QueryWrapper<ScanDependencie> wrapper = new QueryWrapper<>();
- wrapper.eq("git_http_url",gitHttpUrl);
- List<ScanDependencie> oldList = dependencieService.list(wrapper);
- List<String> depIds = oldList.stream().map(ScanDependencie::getId).collect(Collectors.toList());
-
- if(depIds.size()>0){
- QueryWrapper<ScanVulnerabilitie> wrapper2 = new QueryWrapper<>();
- wrapper2.in("dependencie_id",depIds);
- vulnerabilitieService.remove(wrapper2);
- }
- dependencieService.remove(wrapper);
-
- //保存扫描到的依赖数据:dependencieVulList
- dependencieService.saveBatch(dependencieVulList);
- //保存扫描到的依赖数据:vulnerabilitiesAll
- vulnerabilitieService.saveBatch(vulnerabilitiesAll);
- System.out.println("--------------->有漏洞的依赖项:"+dependencieVulList.size());
- System.out.println("--------------->漏洞总数:"+vulnerabilitiesAll.size());
- System.out.println("");
- return vulnerabilitiesAll.size();
- }
-
- @Override
- public String createWord(String name, String reportPath, String gitHttpUrl, String gitBranch) throws IOException {
-
- //所有的有漏洞的依赖
- List<ScanDependencie> dependencieList = new ArrayList<>();
- //所有的漏洞清单
- List<ScanVulnerabilitie> vulList = new ArrayList<>();
- //漏洞清单分组
- Map<String, List<ScanVulnerabilitie>> listMap = new HashMap<>();
- //漏洞编码
- List<String> cveIds = new ArrayList<>();
- //对应的cnnvd信息(中文解释)
- Map<String, Cnnvd> cnnvdMap = new HashMap<>();
- //nvd漏洞原始信息(英文内容)
- Map<String, Cve> cveMap = new HashMap<>();
-
- QueryWrapper<ScanDependencie> wrapper1 = new QueryWrapper();
- wrapper1.eq("name",name).eq("git_http_url",gitHttpUrl).orderByAsc("sort_index");
- dependencieList = dependencieService.list(wrapper1);
-
- List<String> depIds = dependencieList.stream().map(ScanDependencie::getId).collect(Collectors.toList());
- if(depIds==null || depIds.size()==0) return null;
-
- QueryWrapper<ScanVulnerabilitie> wrapper2 = new QueryWrapper<>();
- wrapper2.in("dependencie_id",depIds).orderByAsc("id");
- vulList = vulnerabilitieService.list(wrapper2);
- //漏洞根据依赖id分组
- listMap = vulList.stream().collect(Collectors.groupingBy(ScanVulnerabilitie::getDependencieId));
-
- cveIds = vulList.stream().map(ScanVulnerabilitie::getName).distinct().collect(Collectors.toList());
- //根据漏洞编码查询cnnvd漏洞库
- QueryWrapper<Cnnvd> wrapper3 = new QueryWrapper<>();
- wrapper3.in("cve_id",cveIds);
- List<Cnnvd> nvdList = this.list(wrapper3);
- cnnvdMap = nvdList.stream().collect(Collectors.toMap(Cnnvd::getCveId, Function.identity()));
-
- //根据漏洞编码查询nvd漏洞库
- QueryWrapper<Cve> wrapper4 = new QueryWrapper<>();
- wrapper4.in("id",cveIds);
- List<Cve> cveList = cveService.list(wrapper4);
- cveMap = cveList.stream().collect(Collectors.toMap(Cve::getId, Function.identity()));
-
-
- //--------------------------------开始准备word数据----------------------------------//
- for(ScanDependencie d : dependencieList){
-
- if(!listMap.containsKey(d.getId())) continue;
- List<ScanVulnerabilitie> vuls = listMap.get(d.getId());
- for(ScanVulnerabilitie vul : vuls){
- if(cnnvdMap.containsKey(vul.getName())){
- Cnnvd cnnvd = cnnvdMap.get(vul.getName());
- if(cnnvd==null) continue;
-
- vul.setSeverity(cnnvd.getSeverity());
- vul.setVulnType(cnnvd.getVulnType());
- vul.setCnnvd(cnnvd.getId());
- vul.setTitle(cnnvd.getName());
- vul.setDescription(cnnvd.getDescription());
- }else{
- Cve cve = cveMap.get(vul.getName());
- if(cve==null) continue;
-
- vul.setSeverity(cve.getSeverity());
- vul.setDescription(cve.getDescription());
- }
- }
-
- d.setVulnerabilities(vuls);
- }
-
-
-
-
-
- Map<String,Object> map = new HashMap<>();
- map.put("gitHttpUrl", gitHttpUrl);
- map.put("gitBranch", gitBranch);
- map.put("reportDate", DateUtil.today());
- map.put("projectName",name);
- map.put("vulnerabilitieDependencie",dependencieList.size());
- map.put("vulnerabilitieNum",vulList.size());
- map.put("dependencieList",dependencieList);
- map.put("depList",dependencieList);
- map.put("vulList",vulList);
-
- LoopRowTableRenderPolicy policy = new LoopRowTableRenderPolicy();
- Configure config = Configure.builder().bind("depList", policy).bind("vulList", policy).build();
-
- String fileName = name+"-第三方依赖CVE漏洞扫描报告.docx";
- String outputFile = reportPath+fileName;
- XWPFTemplate template = XWPFTemplate.compile(this.getClass().getClassLoader().getResourceAsStream("templates/第三方依赖CVE漏洞扫描报告模板.docx"),config).render(map);
- template.write(new FileOutputStream(outputFile));
- PoitlIOUtils.closeQuietlyMulti(template);
- return fileName;
- }
-
-
-
-
-
- public String readFileContent(String filePath) throws IOException {
-
- // 指定文件路径
- File file = new File(filePath);
-
- // 创建 BufferedReader 和 InputStreamReader 实例以读取文件
- FileInputStream fileInputStream = new FileInputStream(file);
- InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream, "UTF-8");
- BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
-
- // 创建 StringBuilder 实例存储文件内容
- StringBuilder sb = new StringBuilder();
- String line;
- while ((line = bufferedReader.readLine()) != null) {
- sb.append(line);
- sb.append("\n"); // 每行后添加换行符
- }
-
- // 关闭 BufferedReader 和 InputStreamReader
- bufferedReader.close();
- inputStreamReader.close();
- fileInputStream.close();
-
- // 返回文件内容的字符串形式
- return sb.toString();
- }
-
- }
我这里是使用jenkins的流水线SCM
- def nameStr = 'xxx'
- def gitHttpUrl = 'https://git.xxx.com/aaaa/xxx.git'
- def gitsshUrl = 'git@git.xxx.com:aaaa/xxx.git'
- def gitBranch = 'master'
-
- pipeline {
- agent any
- stages {
- stage('拉取代码') {
- steps {
- script{
- echo '----拉取代码----'
- git branch: gitBranch, credentialsId: 'xxxxxxxx', url: gitHttpUrl
- }
- }
- }
- stage('编译jar包') {
- steps {
- echo '----编译jar包----'
- sh 'mvn clean package -DskipTests=true'
- }
- }
- stage('依赖检查') {
- steps {
- script{
- echo '----第三方jar包依赖检查-----开始'
- sh '/root/dependency-check/bin/dependency-check.sh --project "'+nameStr+'-服务端第三方依赖CVE漏洞扫描报告" --scan "**/*.jar" -n -f JSON -o "/var/www/html/'+nameStr+'-服务端第三方依赖CVE漏洞扫描报告.json"'
- echo '----第三方jar包依赖检查-----结束'
- }
- }
- }
- stage('生成报告') {
- steps {
- script{
- echo '----开始-生成报告----'
- def response = sh(returnStdout: true, script: 'curl -G -s --data-urlencode "name='+nameStr+'" --data-urlencode "sourePath=/var/www/html/'+nameStr+'-服务端第三方依赖CVE漏洞扫描报告.json" --data-urlencode "reportPath=/var/www/html/" --data-urlencode "gitHttpUrl='+gitHttpUrl+'" --data-urlencode "gitBranch='+gitBranch+'" "http://localhost:9000/api/v1/report"')
- echo response
- echo '报告访问地址:http://192.168.1.x/'+response
- echo '----结束-生成报告----'
- }
- }
- }
- }
- }
1、DependencyCheck有对应的maven插件,开发者可以单独集成使用。
2、DependencyCheck可以直接输出html报告,全英文的,有很多信息我们并不关心。
3、本篇文章里面涉及到的提取cve数据和cnnvd数据到数据,解析json和生成word报告,我单独做了一个服务全部整合进去了,jenkins自动化其实就是拉代码,打jar包,调用DependencyCheck扫描jar包并输出json报告,然后再调用我自己整个的这个服务,把报告的名称,git仓库地址,json漏洞报告的路径,以及word最终存放的路径作为参数传进去。最后在jenkins控制点打印一个下载地址。方便下载扫描报告。
4、后续我有时间了会将这个项目整理一下放到开源网站上,静待后续...
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。