Java 兼容读取WPS和Office图片,结合EasyExcel读取单元格信息
在Java开发中,处理Excel文件中的图片(包括浮动图片和嵌入图片)是一个常见的需求。本文将介绍如何使用EasyExcel和Apache POI库来读取Excel文件中的图片,并将其与数据进行关联。
1. 引言
在许多应用场景中,Excel文件不仅包含数据,还可能嵌入了图片。为了有效地提取这些图片并与数据关联,我们需要使用合适的库和方法。本文将详细介绍如何实现这一过程。
浮动图片
内嵌图片
pom
<dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>3.3.2</version>
</dependency>
<dependency><groupId>org.jdom</groupId><artifactId>jdom2</artifactId><version>2.0.6</version>
</dependency>
<dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId><version>3.9</version>
</dependency>
java代码
public class MySAXParserHandler extends DefaultHandler {String value = null;List<String> rows = new ArrayList<>();int rowIndex = 0;public List<String> getRows() {return rows;}/*** 用来标识解析开始*/@Overridepublic void startDocument() throws SAXException {// TODO Auto-generated method stubsuper.startDocument();// System.out.println("SAX解析开始");}/*** 用来标识解析结束*/@Overridepublic void endDocument() throws SAXException {// TODO Auto-generated method stubsuper.endDocument();// System.out.println("SAX解析结束");}/*** 解析xml元素*/@Overridepublic void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {// 调用DefaultHandler类的startElement方法super.startElement(uri, localName, qName, attributes);if (qName.equals("row")) {value = "";}}@Overridepublic void endElement(String uri, String localName, String qName)throws SAXException {//调用DefaultHandler类的endElement方法super.endElement(uri, localName, qName);if (qName.equals("row")) {if (value != null && value.contains("DISPIMG")) {value = value.substring(value.lastIndexOf("DISPIMG(")).replace("DISPIMG(\"", "");value = value.substring(0, value.indexOf("\""));rows.add(rowIndex, value);} else {rows.add(rowIndex, null);}rowIndex++;value = "";}}@Overridepublic void characters(char[] ch, int start, int length)throws SAXException {super.characters(ch, start, length);value += new String(ch, start, length);}
}
@Data
public class BatchSupplierGoodImportForm {private Integer supplierId;private String supplierCode;@Data@NoArgsConstructor@AllArgsConstructorpublic static class GoodItem {@ExcelProperty(value = "产品名称")private String productName;@ExcelImageProperty(value = {"imageUrl"}, index = 2)@ExcelProperty(value = "图片", converter = ExcelUrlImageConverter.class)private String imageUrl;@ExcelProperty(value = "品牌")private String supplierBrand;@ExcelProperty(value = "规格")private String skuDesc;@ExcelProperty(value = "集采价")private String groupPurchasePrice;@ExcelProperty(value = "卖点")private String sellPoint;}
}
public class ExcelImageExtractor2 {public static void main(String[] args) throws Exception {// 测试文件路径File xlsFile = new File("E:\\WeChat Files\\xxx\\FileStorage\\File\\2025-01\\测试.xls");File xlsxFile = new File("E:\\WeChat Files\\xxx\\FileStorage\\File\\2025-01\\测试.xlsx");String fileDir = "E:\\WeChat Files\\xxx\\FileStorage\\File\\2025-01";// 读取 .xls 文件中的浮动图片System.out.println("读取 .xls 文件中的浮动图片...");//readXlsImages(xlsFile, fileDir);// 读取 .xlsx 文件中的浮动图片System.out.println("读取 .xlsx 文件中的浮动图片...");//readXlsxImages(xlsxFile, fileDir);// 读取 .xlsx 文件中的嵌入图片System.out.println("读取 .xlsx 文件中的嵌入图片...");//readXlsxEmbeddedImages2(xlsxFile, fileDir);System.out.println("读取 ");importExcelSpuItem(xlsxFile, fileDir);}@SneakyThrowspublic static void importExcelSpuItem(File originalFile, String fileDir) {try {// 将上传的文件缓存到内存中,方便多次读取byte[] fileBytes = FileUtils.readFileToByteArray(originalFile);// 第一步:动态读取 Excel 文件的表头和所有数据List<Map<Integer, String>> headerList = new ArrayList<>();List<Map<Integer, String>> rawDataList = new ArrayList<>();List<BatchSupplierGoodImportForm.GoodItem> goodItemExcelInputs = new ArrayList<>();EasyExcel.read(new ByteArrayInputStream(fileBytes)).registerReadListener(new AnalysisEventListener<Map<Integer, String>>() {@Overridepublic void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {headerList.add(headMap); // 捕获表头信息}@Overridepublic void invoke(Map<Integer, String> data, AnalysisContext context) {// 检查是否为空行if (isEmptyRow(data)) {return; // 跳过空行}rawDataList.add(data); // 捕获每行数据}@Overridepublic void doAfterAllAnalysed(AnalysisContext context) {// 不需要额外操作}}).sheet().doRead();// 检查是否成功读取到表头if (headerList.isEmpty()) {throw new RuntimeException("Excel 文件中没有表头");}// 第二步:读取 Excel 文件中的图片Map<Integer, String> imagePathMap = new HashMap<>(); // 行号 -> 图片路径readXlsxImages(originalFile, fileDir, imagePathMap);readXlsxEmbeddedImages2(originalFile, fileDir, imagePathMap);// 第三步:将图片与每行数据对应for (int i = 0; i < rawDataList.size(); i++) {Map<Integer, String> rowData = rawDataList.get(i);BatchSupplierGoodImportForm.GoodItem item = new BatchSupplierGoodImportForm.GoodItem();// 提取每列数据item.setProductName(rowData.get(0));item.setImageUrl(imagePathMap.getOrDefault(i + 1, null)); // 根据行号获取图片路径item.setSupplierBrand(rowData.get(2));item.setSkuDesc(rowData.get(3));item.setGroupPurchasePrice(rowData.get(4));item.setSellPoint(rowData.get(5));// 添加到列表goodItemExcelInputs.add(item);// 输出每行数据的文本内容和图片路径//System.out.println("行号: " + i);//System.out.println("数据: " + rowData);//System.out.println("图片路径: " + imagePathMap.getOrDefault(i, "无图片"));}System.out.println(goodItemExcelInputs);} catch (IOException e) {throw new RuntimeException("文件读取失败", e);}}/*** 判断是否为空行*/private static boolean isEmptyRow(Map<Integer, String> data) {for (String value : data.values()) {if (StringUtils.isNotBlank(value)) {return false;}}return true;}// 读取 .xls 文件中的浮动图片public static void readXlsImages(File file, String fileDir) throws Exception {HSSFWorkbook workbook = new HSSFWorkbook(new FileInputStream(file));for (Sheet sheet : workbook) {HSSFSheet hssSheet = (HSSFSheet) sheet;HSSFPatriarch drawingPatriarch = hssSheet.getDrawingPatriarch();if (drawingPatriarch != null) {List<HSSFShape> shapes = drawingPatriarch.getChildren();for (HSSFShape shape : shapes) {if (shape instanceof HSSFPicture) {HSSFPicture pic = (HSSFPicture) shape;HSSFPictureData picData = pic.getPictureData();HSSFClientAnchor anchor = (HSSFClientAnchor) shape.getAnchor();String key = anchor.getRow1() + "行," + anchor.getCol1() + "列";byte[] data = picData.getData();String suffix = picData.suggestFileExtension();File dir = new File(fileDir);if (!dir.exists()) dir.mkdirs();FileUtils.writeByteArrayToFile(new File(dir, key + "." + suffix), data);}}}}workbook.close();}// 读取 .xlsx 文件中的浮动图片public static void readXlsxImages(File file, String fileDir, Map<Integer, String> imagePathMap) throws Exception {XSSFWorkbook workbook = new XSSFWorkbook(new FileInputStream(file));for (Sheet sheet : workbook) {XSSFSheet xssSheet = (XSSFSheet) sheet;XSSFDrawing drawing = xssSheet.getDrawingPatriarch();if (drawing != null) {List<XSSFShape> shapes = drawing.getShapes();for (XSSFShape shape : shapes) {if (shape instanceof XSSFPicture) {XSSFPicture pic = (XSSFPicture) shape;XSSFPictureData picData = pic.getPictureData();XSSFClientAnchor anchor = (XSSFClientAnchor) shape.getAnchor();String key = anchor.getRow1() + "行," + anchor.getCol1() + "列";byte[] data = picData.getData();String suffix = picData.suggestFileExtension();File dir = new File(fileDir);if (!dir.exists()) dir.mkdirs();FileUtils.writeByteArrayToFile(new File(dir, key + "." + suffix), data);imagePathMap.put(anchor.getRow1(), key + "." + suffix);}}}}workbook.close();}// 读取 .xlsx 文件中的嵌入图片public static void readXlsxEmbeddedImages2(File file, String fileDir, Map<Integer, String> imagePathMap) throws Exception {OPCPackage opcPackage = OPCPackage.open(new FileInputStream(file));List<PackagePart> parts = opcPackage.getParts();Map<Integer, List<PackagePart>> picturePath = getEmbedPictures(parts);for (Integer sheetIndex : picturePath.keySet()) {List<PackagePart> rows = picturePath.get(sheetIndex);for (int i = 0; i < rows.size(); i++) {PackagePart part = rows.get(i);if (part != null) {InputStream imgIs = part.getInputStream();String name = part.getPartName().getName();// 从 XML 文件中解析行和列信息String cellPosition = getCellPosition(sheetIndex, i, parts);// 保存图片到本地File dir = new File(fileDir);if (!dir.exists()) dir.mkdirs();FileUtils.copyInputStreamToFile(imgIs, new File(dir, "工作表" + sheetIndex + "_" + cellPosition + "_" + name.substring(name.lastIndexOf("/") + 1)));imagePathMap.put(i, cellPosition + "_" + name.substring(name.lastIndexOf("/") + 1));}}}opcPackage.close();}/*** 获取图片所在的单元格位置(行和列)。** @param sheetIndex 工作表索引* @param imageIndex 图片索引* @param parts 压缩包中的所有部分* @return 单元格位置(如 "5行_3列")*/private static String getCellPosition(int sheetIndex, int imageIndex, List<PackagePart> parts) throws Exception {// 解析 `/xl/worksheets/sheet*.xml` 文件,获取行和列信息for (PackagePart part : parts) {String name = part.getPartName().getName();if (name.equals("/xl/worksheets/sheet" + (sheetIndex + 1) + ".xml")) {SAXParserFactory factory = SAXParserFactory.newInstance();SAXParser parser = factory.newSAXParser();MySAXParserHandler handler = new MySAXParserHandler();parser.parse(part.getInputStream(), handler);// 获取图片对应的行和列信息List<String> rows = handler.getRows();if (imageIndex < rows.size()) {return rows.get(imageIndex); // 返回行和列信息}}}return "未知行_未知列"; // 如果未找到,返回默认值}@SneakyThrowsprivate static Map<Integer, List<PackagePart>> getEmbedPictures(List<PackagePart> parts) {Map<String, Set<String>> mapImg = new HashMap<>();Map<String, String> mapImgPath = new HashMap<>();Map<Integer, List<String>> dataMap = new HashMap<>();for (PackagePart part : parts) {
// System.out.println(part.getPartName());PackagePartName partName = part.getPartName();String name = partName.getName();if ("/xl/cellimages.xml".equals(name)) {SAXBuilder builder = new SAXBuilder();// 获取文档Document doc = builder.build(part.getInputStream());// 获取根节点Element root = doc.getRootElement();List<Element> cellImageList = root.getChildren();for (Element imgEle : cellImageList) {Element xdrPic = imgEle.getChildren().get(0);Element xdrNvPicPr = xdrPic.getChildren().get(0);Element xdrBlipFill = xdrPic.getChildren().get(1);Element aBlip = xdrBlipFill.getChildren().get(0);Attribute attr = aBlip.getAttributes().get(0);String imgId = xdrNvPicPr.getChildren().get(0).getAttributeValue("name");String id = attr.getValue();
// if (id.equals("rId12")) {
// System.out.println(attr.getValue() + "\t" + imgId);
// }if (mapImg.containsKey(id)) {mapImg.get(id).add(imgId);} else {Set<String> set = new HashSet<>();set.add(imgId);mapImg.put(id, set);}}}if ("/xl/_rels/cellimages.xml.rels".equals(name)) {SAXBuilder builder = new SAXBuilder();// 获取文档Document doc = builder.build(part.getInputStream());// 获取根节点Element root = doc.getRootElement();List<Element> relationshipList = root.getChildren();/*<Relationship Id="rId999" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="media/image1000.jpeg"/>*/for (Element relationship : relationshipList) {String id = relationship.getAttributeValue("Id");String target = relationship.getAttributeValue("Target");mapImgPath.put(id, target);
// if (id.equals("rId12")) {
// System.out.println(id + "\t" + target);
// }}}if (name.contains("/xl/worksheets/sheet")) {
// SAXBuilder builder = new SAXBuilder();// 获取文档String sheetNoStr = name.replace("/xl/worksheets/sheet", "").replace(".xml", "");Integer sheetNo = Integer.valueOf(sheetNoStr) - 1;// 步骤1:创建SAXParserFactory实例SAXParserFactory factory = SAXParserFactory.newInstance();// 步骤2:创建SAXParser实例SAXParser parser = factory.newSAXParser();MySAXParserHandler handler = new MySAXParserHandler();parser.parse(part.getInputStream(), handler);List<String> rows = handler.getRows();dataMap.put(sheetNo, rows);}}// for (Integer sheetNo : dataMap.keySet()) {
// System.out.println(sheetNo + "\t" + dataMap.get(sheetNo).size());
// }Map<String, String> imgMap = new HashMap<>();for (String id : mapImg.keySet()) {Set<String> imgIds = mapImg.get(id);String path = mapImgPath.get(id);for (String imgId : imgIds) {imgMap.put(imgId, path);}}for (Integer key : dataMap.keySet()) {List<String> rows = dataMap.get(key);for (int i = 0; i < rows.size(); i++) {String imgId = rows.get(i);if (imgMap.containsKey(imgId)) {rows.set(i, imgMap.get(imgId));}}}Map<Integer, List<PackagePart>> map = new HashMap<>();for (Integer key : dataMap.keySet()) {List<PackagePart> list = new ArrayList<>();map.put(key, list);List<String> pathList = dataMap.get(key);for (int i = 0; i < pathList.size(); i++) {list.add(i, null);String path = pathList.get(i);if (StringUtils.isNotEmpty(path)) {for (PackagePart part : parts) {PackagePartName partName = part.getPartName();String name = partName.getName();// /xl/media/image373.jpeg = media/image702.jpegif (name.contains(path)) {list.set(i, part);break;}}}}}return map;}
}
参考链接:https://blog.csdn.net/qq_23123177/article/details/133638391
相关文章:

Java 兼容读取WPS和Office图片,结合EasyExcel读取单元格信息
在Java开发中,处理Excel文件中的图片(包括浮动图片和嵌入图片)是一个常见的需求。本文将介绍如何使用EasyExcel和Apache POI库来读取Excel文件中的图片,并将其与数据进行关联。 1. 引言 在许多应用场景中,Excel文件不…...

电脑硬盘系统迁移及问题处理
一、系统迁移准备 1、确认你的电脑主板是否支持安装两块硬盘,如电脑主板有多个M2硬盘接口,我们将新硬盘安装到主板上,原来的老硬盘安装在第二个接口上,主板只有一个M2接口的话可以使用移动硬盘盒。 2、新硬盘安装好后,我们进入原来的系统,在 此电脑–右键–管理–磁盘管…...

网关 + Nacos配置管理
网关 网关:就是网络的关口,负责请求的路由、转发、身份校验。 网关路由 新建网关模块gateway引入相关依赖 <!--网关--> <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter…...

《Spring Framework实战》6:核心技术 4.1.IoC 容器
欢迎观看《Spring Framework实战》视频教程 本章介绍 Spring 的控制反转 (IoC) 容器。 本部分摘要 Spring IoC 容器和 Bean 简介 容器概述 Bean 概述 依赖 Bean 作用域 自定义 Bean 的性质 Bean 定义继承 容器扩展点 基于注解的容器配置 Clas…...
ModuleNotFoundError: No module named ‘audioop‘
问题 ModuleNotFoundError: No module named pyaudioop ModuleNotFoundError: No module named audioop解决方案 安装库 pip3 install audioop-lts...

STM32-笔记38-I2C-oled实验
一、什么是I2C? I2C总线,全称Inter-Integrated Circuit(互连集成电路),是一种由Philips(现NXP半导体)公司在1980年代初开发的同步 串行 半双工通信总线。 二、有了串口通信为什么要使用I2C&…...

人大金仓实现主键自增.
使用数据库中自带的参数类型 serial 类型(相当于创建一个INT列), 或者bigserial(相当于创建一个BIGINT列. 示例sql: CREATE TABLE ord(id SERIAL,ord_no INT NOT NULL,ord_name VARCHAR(32),CONSTRAINT "ord_PKEY" PRIMARY KEY ("id"));插入时指定自增值…...

h264之多视点mvc编码及解码过程(JMVC平台举例)
h264标准参考平台JMVC是针对MVC标准的,JMVC支持多视点编码、合流、多视点解码操作。可以利用JMVC生成h264 mvc码流和解码。 JMVC的下载地址是:jvet / JMVC GitLabH.264/AVC multi-view coding (MVC) extension JMVC reference softwarehttps://vcgit.hh…...

小程序学习08—— 系统参数获取和navBar组件样式动态设置
一 系统信息的概念 uni-app提供了异步(uni.getSystemInfo)和同步(uni.getSystemInfoSync)的2个API获取系统信息。 success 返回参数说明: 参数分类说明statusBarHeight手机状态栏的高度system操作系统名称及版本。。。 二 自定义navbar 2.1 获取系统参数 代码展示…...

数据库环境安装(day1)
网址:MySQL 下载(环境准备): (2-5点击此处,然后选择合适的版本) 1.linux在线YUM仓库 下载/安装: wget https://repo.mysql.com//mysql84-community-release-el9-1.noarch.rpm rpm -i https://r…...

网络安全-web渗透环境搭建-BWAPP(基础篇)
01--所需系统环境: 虚拟主机系统部署(vmware,虚拟主机创建、虚拟主机网络配置(桥接,便于网络中多个主机都能访问虚拟主机)、虚拟软件功能,快照、克隆、镜像文件加载,ova文件制作&am…...

当算法遇到线性代数(三):实对称矩阵
实对称矩阵的理论与应用 线性代数系列相关文章(置顶) 1.当算法遇到线性代数(一):二次型和矩阵正定的意义 2.当算法遇到线性代数(二):矩阵特征值的意义 3.当算法遇到线性代数&#x…...

WayLand的架构和协议
WayLand的架构和协议 1. Wayland简介 1.1 Wayland是啥?为啥它这么重要? 嘿,你知道吗?有时候咱们用电脑的时候,是不是觉得图形界面有点慢、有点卡?那是因为我们还在用一个叫X Window System (X11)的老伙计…...

STM32学习(十)
I2C模块内部结构 I2C(Inter-Integrated Circuit)模块是一种由Philips公司开发的二线式串行总线协议,用于短距离通信,允许多个设备共享相同的总线。 硬件连接简单:I2C通信仅需要两条总线,即SCL&…...

进阶篇-Day17:JAVA的日志、枚举、类加载器、反射等介绍】
目录 1、日志1.1 日志概念1.2 日志框架(1) Logback框架:(2)配置文件介绍: 2、枚举3、类加载器3.1 类加载器的介绍3.2 类加载器的加载过程:加载、链接、初始化3.3 类加载器的分类3.4 双亲委派模式…...

Java设计模式 —— 【行为型模式】责任链模式(Chain-of-responsibility Pattern) 详解
文章目录 模式介绍优缺点适用场景模式结构案例实现 模式介绍 责任链模式又名职责链模式,它是一种对象行为的设计模式,为了避免请求发送者与多个请求处理者耦合在一起,将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链&…...
C++和Python中负数取余结果的区别
C中的负数取余规则(取模%)规则 在C中,取余运算( % )的结果符号与被除数相同。从数学定义角度看,C遵循的是尽量让商向零取整的规则。例如计算 -7/3,商是 -2 (向零取整)&a…...

rust学习——环境搭建
rust安装:https://kaisery.github.io/trpl-zh-cn/ch01-01-installation.html 1、vscode装插件: toml语法支持 依赖管理 rust语法支持 2、创建demo 3、查看目录 4、执行文件的几种方式: rust安装:https://www.rust-lang.org/z…...

Linux系统中解决端口占用问题
在日常的 Linux 系统管理和开发过程中,端口占用是一个常见且令人头疼的问题。无论是部署新服务、调试应用程序,还是进行系统维护,遇到端口被占用都可能导致服务无法正常启动或运行。本文将详细介绍在 Linux 系统中如何识别和解决端口占用问题…...
现代软件架构设计:14个质量属性的定义、权衡与最佳实践
1. 引言 1.1 技术架构的重要性 技术架构是指导软件系统设计和开发的核心,它定义了系统的高层结构及关键技术选型。一个优秀的技术架构可以提高开发效率、系统稳定性和扩展能力,确保项目成功落地。面对复杂业务场景,技术架构的设计至关重要&…...

C++实现分布式网络通信框架RPC(3)--rpc调用端
目录 一、前言 二、UserServiceRpc_Stub 三、 CallMethod方法的重写 头文件 实现 四、rpc调用端的调用 实现 五、 google::protobuf::RpcController *controller 头文件 实现 六、总结 一、前言 在前边的文章中,我们已经大致实现了rpc服务端的各项功能代…...

关于nvm与node.js
1 安装nvm 安装过程中手动修改 nvm的安装路径, 以及修改 通过nvm安装node后正在使用的node的存放目录【这句话可能难以理解,但接着往下看你就了然了】 2 修改nvm中settings.txt文件配置 nvm安装成功后,通常在该文件中会出现以下配置&…...
基于Uniapp开发HarmonyOS 5.0旅游应用技术实践
一、技术选型背景 1.跨平台优势 Uniapp采用Vue.js框架,支持"一次开发,多端部署",可同步生成HarmonyOS、iOS、Android等多平台应用。 2.鸿蒙特性融合 HarmonyOS 5.0的分布式能力与原子化服务,为旅游应用带来…...

ETLCloud可能遇到的问题有哪些?常见坑位解析
数据集成平台ETLCloud,主要用于支持数据的抽取(Extract)、转换(Transform)和加载(Load)过程。提供了一个简洁直观的界面,以便用户可以在不同的数据源之间轻松地进行数据迁移和转换。…...

【配置 YOLOX 用于按目录分类的图片数据集】
现在的图标点选越来越多,如何一步解决,采用 YOLOX 目标检测模式则可以轻松解决 要在 YOLOX 中使用按目录分类的图片数据集(每个目录代表一个类别,目录下是该类别的所有图片),你需要进行以下配置步骤&#x…...

ElasticSearch搜索引擎之倒排索引及其底层算法
文章目录 一、搜索引擎1、什么是搜索引擎?2、搜索引擎的分类3、常用的搜索引擎4、搜索引擎的特点二、倒排索引1、简介2、为什么倒排索引不用B+树1.创建时间长,文件大。2.其次,树深,IO次数可怕。3.索引可能会失效。4.精准度差。三. 倒排索引四、算法1、Term Index的算法2、 …...

关键领域软件测试的突围之路:如何破解安全与效率的平衡难题
在数字化浪潮席卷全球的今天,软件系统已成为国家关键领域的核心战斗力。不同于普通商业软件,这些承载着国家安全使命的软件系统面临着前所未有的质量挑战——如何在确保绝对安全的前提下,实现高效测试与快速迭代?这一命题正考验着…...
重启Eureka集群中的节点,对已经注册的服务有什么影响
先看答案,如果正确地操作,重启Eureka集群中的节点,对已经注册的服务影响非常小,甚至可以做到无感知。 但如果操作不当,可能会引发短暂的服务发现问题。 下面我们从Eureka的核心工作原理来详细分析这个问题。 Eureka的…...
#Uniapp篇:chrome调试unapp适配
chrome调试设备----使用Android模拟机开发调试移动端页面 Chrome://inspect/#devices MuMu模拟器Edge浏览器:Android原生APP嵌入的H5页面元素定位 chrome://inspect/#devices uniapp单位适配 根路径下 postcss.config.js 需要装这些插件 “postcss”: “^8.5.…...

网站指纹识别
网站指纹识别 网站的最基本组成:服务器(操作系统)、中间件(web容器)、脚本语言、数据厍 为什么要了解这些?举个例子:发现了一个文件读取漏洞,我们需要读/etc/passwd,如…...