OFD 发票解析

news2024/9/27 10:19:51

文章目录

  • 参考文章
  • 1 了解ofd文件结构
    • 1.1 如何打开ofd 文件
    • 1.2 ofd文件结构
    • 1.3 提取信息思路
  • 2. 提取发票信息实现
    • 2.1 目录结构
    • 2.2 实体类
    • 2.3 发票解析类
    • 2.4 controller
    • 2.5 service

参考文章

ofd发票解析
什么是ofd格式

  • ofd 格式是一种用于存储金融数据的开放格式,它可以包含各种类型的金融信息,通常一XML格式进行存储,因此我们可以使用java中的xml解析器来解析ofd文件并提取其中的数据。

  • 在 java 中,我们可以使用DOM解析器来解析XML文件。然后编写代码来读取OFD 文件并解析其中的数据。

1 了解ofd文件结构

1.1 如何打开ofd 文件

  • 可以把ofd文件后缀改为zip,再进行解压就可以看到文件结构了

1.2 ofd文件结构

  1. 解压缩 ofd 文件,正常情况下在 Doc_0 目录下有 Annots、Pages、Res、Tags、Tpls文件夹
    及 Document.xml、DocumentRes.xml、PublicRes.xml 等文件;
    • Pages/Page_0/Content.xml :存放value信息(每一条value内容、位置、字体、字号、ID号),
    • Tags/CustomTag.xml:存放“key”信息(定义的英文key及ID号,注意:个别字段有可能无“key”定义),

一般情况下根据“key”信息与value信息一一映射,可恢复票面上所有字段
在这里插入图片描述

以下信息有所删减

Tags/CustomTag.xml:存放“key”信息【例如InvoiceNoIssueDateNote

This XML file does not appear to have any style information associated with it. The document tree is shown below.
<ofd:root xmlns:ofd="http://www.ofdspec.org/2016" version="1.0">
<ofd:InvoiceNo>
<ofd:ObjectRef PageRef="61">6922</ofd:ObjectRef>
</ofd:InvoiceNo>
<ofd:IssueDate>
<ofd:ObjectRef PageRef="61">6923</ofd:ObjectRef>
</ofd:IssueDate>
<ofd:TaxInclusiveTotalAmount>
<ofd:ObjectRef PageRef="61">6935</ofd:ObjectRef>
<ofd:ObjectRef PageRef="61">6936</ofd:ObjectRef>
</ofd:TaxInclusiveTotalAmount>
<ofd:Note>
<ofd:ObjectRef PageRef="61">6944</ofd:ObjectRef>
<ofd:ObjectRef PageRef="61">6945</ofd:ObjectRef>
<ofd:ObjectRef PageRef="61">6946</ofd:ObjectRef>
<ofd:ObjectRef PageRef="61">6947</ofd:ObjectRef>
</ofd:Note>
</ofd:root>

Pages/Page_0/Content.xml :

This XML file does not appear to have any style information associated with it. The document tree is shown below.
<ofd:Page xmlns:ofd="http://www.ofdspec.org/2016">
<ofd:Area>
<ofd:PhysicalBox>0 0 210 140</ofd:PhysicalBox>
</ofd:Area>
<ofd:Template TemplateID="1" ZOrder="Background"/>
<ofd:Content>
<ofd:Layer ID="6948">
<ofd:TextObject ID="6922" Boundary="170 10.3 38 5" Font="6919" Size="3.175">
<ofd:TextCode X="0" Y="3.6414" DeltaX="g 19 1.5875">发票编号</ofd:TextCode>
</ofd:TextObject>
<ofd:TextObject ID="6923" Boundary="170 16.4 38 5" Font="6919" Size="3.175">
<ofd:TextCode X="0" Y="3.6414" DeltaX="g 4 1.5875 3.175 g 2 1.5875 3.175 g 2 1.5875">发票日期</ofd:TextCode>
</ofd:TextObject>
<ofd:TextObject ID="6934" Boundary="62.9471 96 82.5189 7.4102" Font="6919" Size="3.175">
<ofd:TextCode X="0" Y="4.8465" DeltaX="g 5 3.175">发票金额中文</ofd:TextCode>
</ofd:TextObject>
<ofd:TextObject ID="6935" Boundary="155.5164 96.0681 48.665 7.6749" Font="6925" Size="3.8806">
<ofd:TextCode X="0" Y="4.9221">¥</ofd:TextCode>
</ofd:TextObject>
<ofd:TextObject ID="6936" Boundary="155.5164 96.0681 48.665 7.6749" Font="6919" Size="3.8806">
<ofd:TextCode X="2.3284" Y="4.9221" DeltaX="g 6 1.9403">发票金额阿拉伯数字</ofd:TextCode>
</ofd:TextObject>
<ofd:TextObject ID="6944" Boundary="11.1083 104.5369 193 3.3" Font="6919" Size="3.175">
<ofd:TextCode X="0" Y="2.7273" DeltaX="g 6 3.175 1.5875 g 10 3.175 g 5 1.5875 g 4 3.175 g 20 1.5875">购方开户银行:XXX支行; 银行账号:1234567890; </ofd:TextCode>
</ofd:TextObject>
<ofd:TextObject ID="6945" Boundary="11.1083 107.8369 193 3.3" Font="6919" Size="3.175">
<ofd:TextCode X="0" Y="2.7273" DeltaX="g 6 3.175 1.5875 g 16 3.175 g 5 1.5875 g 4 3.175 g 24 1.5875">销方开户银行:XXX支行; 银行账号:1234567890; </ofd:TextCode>
</ofd:TextObject>
<ofd:TextObject ID="6946" Boundary="11.1083 111.1369 193 3.3" Font="6919" Size="3.175">
<ofd:TextCode X="0" Y="2.7273" DeltaX="g 3 1.5875"> </ofd:TextCode>
</ofd:TextObject>
<ofd:TextObject ID="6947" Boundary="11.1083 114.4369 193 3.3" Font="6919" Size="3.175">
<ofd:TextCode X="0" Y="2.7273" DeltaX="g 3 3.175 1.5875 g 2 3.175 g 5 1.5875 g 3 3.175 1.5875 g 2 3.175 g 4 1.5875">收款人:XX; 复核人:XX; </ofd:TextCode>
</ofd:TextObject>
<ofd:ImageObject ID="6921" CTM="20 0 0 20 0 0" Boundary="6.5 6 20 20" ResourceID="6920"/>
</ofd:Layer>
</ofd:Content>
</ofd:Page>

1.3 提取信息思路

  1. 读取 Tags/CustomTag.xml 文件根据“key”信息【例如InvoiceNoIssueDateNote】遍历其中的标签获得Pages/Page_0/Content.xml 文件中对应的 ID 的值如 6922
  2. 扫描 Pages/Page_0/Content.xml 文件,根据之前获得的 ID 的值,获得想要提取的内容

2. 提取发票信息实现

2.1 目录结构

在这里插入图片描述
添加Maven依赖

<dependency>
            <groupId>org.dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>2.1.3</version>
        </dependency>

2.2 实体类

仅有一个实体类Invoice

package com.example.ofd.entity;

import lombok.Data;

@Data
public class Invoice {
    private String invoiceNo;// 发票编号
    private String issueDate;// 开票日期
    private String totalAmount;// 开票金额
    private String note;//开票备注
}

2.3 发票解析类

package com.example.ofd.utils;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.nio.charset.Charset;

import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

import com.example.ofd.entity.Invoice;
import lombok.extern.slf4j.Slf4j;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.springframework.util.StreamUtils;
import org.springframework.web.multipart.MultipartFile;


/**
 * 专用于处理电子发票识别的类
 */
@Slf4j
public class OfdInvoice {
    /**
     * 调用该方法将前端接受到的文件暂存
     *
     * @param file
     */
    public static Invoice parseOfdFile(MultipartFile file) {
        Invoice invoice = new Invoice();
        // 先判断提交上来的文件是什么类型的
        String originalFilename = file.getOriginalFilename();
        try {
            // 创建一个临时文件
            Path tempFile = null;
            if (originalFilename.toLowerCase().endsWith(".ofd")) {
                tempFile = Files.createTempFile("tempPrefix", ".ofd");
            } else if (originalFilename.toLowerCase().endsWith(".pdf")) {
                tempFile = Files.createTempFile("tempPrefix", ".pdf");
            }

            File tempFilePath = tempFile.toFile();

            // 将MultipartFile的内容写入到临时文件
            try (FileOutputStream fos = new FileOutputStream(tempFilePath)) {
                fos.write(file.getBytes());
            }

            // 使用临时文件的路径来调用你的解析方法
            invoice = extract(tempFilePath);

            // 删除临时文件,或者在某些情况下保留它
            tempFilePath.delete();


        } catch (Exception e) {
            // 处理异常
            e.printStackTrace();
        }
        // 返回值
        return invoice;
    }

    /**
     * 从一个ZIP 文件中提取特定格式的发票信息,并构建一个 Invoice 对象来存储这些信息
     *
     * @param file
     * @return
     * @throws IOException
     * @throws DocumentException
     */
    public static Invoice extract(File file) throws IOException, DocumentException {
        // 打开Zip文件
        ZipFile zipFile = new ZipFile(file);
        // 获取Zip条目
        ZipEntry entry = zipFile.getEntry("Doc_0/Tags/CustomTag.xml"); // 标签文件,在本文件中根据key信息,获取到标签的ID
        ZipEntry entry1 = zipFile.getEntry("Doc_0/Pages/Page_0/Content.xml"); // 本文件中存放的是value信息,根据上个id来查找各自的value
        // 读取XML文件内容
        InputStream input = zipFile.getInputStream(entry);
        InputStream input1 = zipFile.getInputStream(entry1);
        String body = StreamUtils.copyToString(input, Charset.forName("utf-8")); // 读取xml文件的内容,同时指定字符集为UTF-8
        String content = StreamUtils.copyToString(input1, Charset.forName("utf-8"));
        // 关闭ZIP文件
        zipFile.close();
        Map<String, List<String>> map = new HashMap<>();
        // 解析 CustomTag.xml 文件
        Document document = DocumentHelper.parseText(body); // 解析 CustomTag.xml 的内容,并获取根元素
        Element root = document.getRootElement(); // 获取根元素

        // 发票编号
        Element invoiceNo = root.element("InvoiceNo");// 获取InvoiceNo元素
        if (invoiceNo != null) {
            Element objectRef = invoiceNo.element("ObjectRef");// 获取其下子标签
            if (objectRef != null) {
                String invoiceNumber = objectRef.getTextTrim();// 访问ObjectRef的文本内容
                List<String> tmp = new ArrayList<>();// 将文本内容添加到Map中
                tmp.add(invoiceNumber);
                map.put("InvoiceNumber", tmp);
            }
        }

        // 开票日期
        Element issueDate = root.element("IssueDate");// 获取InvoiceNo元素
        if (issueDate != null) {
            Element objectRef = issueDate.element("ObjectRef");// 获取其下子标签
            if (objectRef != null) {
                String invoiceDate = objectRef.getTextTrim();// 访问ObjectRef的文本内容
                // 将文本内容添加到Map中
                List<String> tmp = new ArrayList<>();
                tmp.add(invoiceDate);
                map.put("invoiceDate", tmp);
            }
        }

        // 开票金额【其下有两个子元素,第二个是想要的标签】
        Element totalAmount = root.element("TaxInclusiveTotalAmount");// 获取InvoiceNo元素
        if (totalAmount != null) {
            // 遍历InvoiceNo下的所有子元素
            for (Iterator<Element> it = totalAmount.elementIterator(); it.hasNext(); ) {
                Element element = it.next();
                if (it.hasNext() == false) {//只要最后一个标签
                    // 检查子元素是否是ObjectRef
                    if ("ObjectRef".equals(element.getName())) {
                        // 访问ObjectRef的文本内容
                        String amount = element.getTextTrim();
                        // 将文本内容添加到Map中
                        List<String> tmp = new ArrayList<>();
                        tmp.add(amount);
                        map.put("totalAmount", tmp);
                    }
                }

            }
        }

        // 开票备注【其下有四条信息,都需要】
        Element note = root.element("Note");// 获取InvoiceNo元素
        if (note != null) {
            List<String> noteTmp = new ArrayList<>();
            for (Iterator<Element> it = note.elementIterator(); it.hasNext(); ) {// 遍历InvoiceNo下的所有子元素
                Element element = it.next();
                // 检查子元素是否是ObjectRef
                if ("ObjectRef".equals(element.getName())) {
                    // 访问ObjectRef的文本内容
                    String tmpNote = element.getTextTrim();
                    // 将文本内容添加到List数组中
                    noteTmp.add(tmpNote);
                }
            }
            map.put("note", noteTmp);// 将文本内容添加到map中
        }

        // 根据id从content.xml中提取必要信息
        Invoice invoice = new Invoice();// 先创建一个发票实例,将后续的到的值填充进去
        Document contentDocument = DocumentHelper.parseText(content);
        Element contentRoot = contentDocument.getRootElement();// 获取根元素

        for (Map.Entry<String, List<String>> entrySet : map.entrySet()) {// 遍历map
            String key = entrySet.getKey(); //获得当前key
            if (key.equals("InvoiceNumber")) {// 发票号码
                invoice.setInvoiceNo(getContent(contentRoot, entrySet.getValue().get(0)));
            } else if (key.equals("invoiceDate")) {// 开票日期
                invoice.setIssueDate(getContent(contentRoot, entrySet.getValue().get(0)));
            } else if (key.equals("totalAmount")) {// 开票金额
                invoice.setTotalAmount(getContent(contentRoot, entrySet.getValue().get(0)));
            } else if (key.equals("note")) {// 发票备注
                String detail = "";
                for (int i = 0; i < entrySet.getValue().size(); i++) {
                    detail += getContent(contentRoot, entrySet.getValue().get(i));
                }
                invoice.setNote(detail);
            }
        }
        return invoice;
    }

    /**
     * 根据id,获取root中的文本
     *
     * @param root xml中的根元素
     * @param id   存储有id
     * @return
     */
    public static String getContent(Element root, String id) {
        // 遍历Content元素
        Element content = root.element("Content");
        if (content != null) {
            // 遍历所有Layer元素
            for (Element layer : content.elements("Layer")) {
                // 遍历Layer下的所有TextObject元素
                for (Element textObject : layer.elements("TextObject")) {
                    // 检查TextObject的ID
                    String textObjectId = textObject.attributeValue("ID");
                    if (id.equals(textObjectId)) {
                        // 找到匹配的TextObject,现在遍历其下的TextCode元素
                        for (Element textCode : textObject.elements("TextCode")) {
                            // 获取TextCode的文本内容
                            String text = textCode.getTextTrim();
                            return text;
                        }
                    }
                }
            }
        }
        return null;
    }
}

2.4 controller

package com.example.ofd.controller;

import com.example.ofd.entity.Invoice;
import com.example.ofd.service.InvoiceService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;


@RestController
@RequestMapping("/invoice")
public class InvoiceController {
    @Autowired
    InvoiceService invoiceService;

    /**
     * @param
     */
    @CrossOrigin(origins = "http://localhost:8081", allowedHeaders = "*", allowCredentials = "true")
    @PostMapping("/upload")
    public ResponseEntity<Object> uploadFile(@RequestParam("file") MultipartFile file) {
        try {
            // 调用你的文件解析服务
            Invoice parsedData = invoiceService.parseOfdFile(file);

            // 返回解析后的数据
            return ResponseEntity.ok(parsedData);
        } catch (Exception e) {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error parsing file");
        }
    }
}

2.5 service

InvoiceServiceImpl

package com.example.ofd.service.impl;

import com.example.ofd.entity.Invoice;
import com.example.ofd.service.InvoiceService;
import com.example.ofd.utils.OfdInvoice;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

@Service
public class InvoiceServiceImpl implements InvoiceService {
    @Override
    public Invoice parseOfdFile(MultipartFile file) {
        Invoice invoice = OfdInvoice.parseOfdFile(file);
        return invoice;
    }
}

InvoiceService

package com.example.ofd.service;

import com.example.ofd.entity.Invoice;
import org.springframework.web.multipart.MultipartFile;

public interface InvoiceService {
    Invoice parseOfdFile(MultipartFile file);

}


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1985403.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringBoot3 配置Logback日志滚动文件

简介 本文介绍了在SpringBoot3中配置Logback日志滚动文件的方法&#xff0c;因为SpringBoot3内置的logback版本是1.4.14&#xff0c;之前使用SpringBoot2.1.5的logback配置发现有些东西不能生效了 环境 SpringBoot v3.3.2 内置的logback-core为1.4.14 正文 <configuration …

【预训练语言模型】 使用Transformers库进行BERT预训练

基于 HuggingFace的Transformer库&#xff0c;在Colab或Kaggle进行预训练。 鉴于算力限制&#xff0c;选用了较小的英文数据集wikitext-2 目的&#xff1a;跑通Mask语言模型的预训练流程 一、准备 1.1 安装依赖 !pip3 install --upgrade pip !pip install -U datasets !pi…

2024华数杯c题题解(一)

目录 原题背景 背景分析 问题一 思路 代码 问题二 思路 代码 原题背景 最近&#xff0c;“city 不 city”这一网络流行语在外国网红的推动下备受关注。随着我国过境免签政策的落实&#xff0c;越来越多外国游客来到中国&#xff0c;通过网络平台展示他们在华旅行的见闻…

如何利用virtuoso自动仿真占空比?

设计完一个振荡器&#xff08;OSC&#xff09;&#xff0c;我们有时候会仿真一下占空比&#xff0c;那么如何利用virtuoso软件的caculator功能自动获取呢&#xff1f; 开整&#xff01; 占空比是指在一个脉冲循环内&#xff0c;通电时间相对于总时间所占的比例。 我们在时钟上…

2023大数据-架构师案例(八)

Lambda架构 nginx &#xff08;b&#xff09; Hbase &#xff08;c&#xff09;Spark Streaming &#xff08;d&#xff09;Spark &#xff08;e&#xff09;MapReduce &#xff08;f&#xff09;ETL &#xff08;g&#xff09;MemSQL &#xff08;h&#xff09;HDFS &#x…

电机物理数学建模

电机定义 电机是以磁场为媒介&#xff0c;利用电磁感应作用进行能量转换与传递的电磁装置。机电能量转换装置&#xff0c;无论尺寸大小&#xff0c;从大型旋转电机如水轮发电机到小型机电信号变换器&#xff0c;虽然它们的用途和结构各异&#xff0c;但都基于相同的电磁场与运…

Moretl 日志采集工具

使用咨询: 扫码添加QQ 永久免费: Gitee下载最新版本 使用说明: CSDN查看使用说明 功能: 定时(全量采集or增量采集) SCADA,MES等系统采集工控机,办公电脑文件. 优势1: 开箱即用. 解压直接运行.插件集成下载. 优势2: 批管理设备. 配置均在后台配置管理. 优势3: 无人值守 采集端…

E2000 RGMII0通讯异常问题总结

最近让新来小朋友做了一款E2000Q的板卡,使用了E2000Q上的两个RGMII资源,外接YT8521转出了电口。 但是他调试中遇到了一个比较奇怪的问题,两套YT8521的电路都一样,但是一路通一路不通。 也就是框图中MAC2(芯片RGMII0,系统对应eth1)那路网络不通,图中MAC3(芯片RGMII1,…

开发Chrome浏览器插件 - 第一步

目录 1. 准备工作 2. 创建基础文件 3. 编写manifest.json 4. 编写popup.html 5. 编写background.js 6. 编写content.js 7. 加载插件 8. 测试和调试 9. 发布插件 9.1 创建开发者账号步骤 9.2 提交Chrome扩展程序步骤 1. 准备工作 安装Chrome浏览器&#xff1a;确保…

C# 中引用类型的探讨

引用类型的变量不直接包含其数据&#xff1b;它包含对其数据的引用。 如果按值传递引用类型参数&#xff0c;则可能更改属于所引 用对象的数据&#xff0c;例如类成员的值。 但是&#xff0c;不能更改引用本身的值&#xff1b;例如&#xff0c;不能使用相同引用为新对象分配内存…

根据年月将数组拆分为以年月为key的二维数组

处理前: 处理后: public function lists(): array{$field = change_type,change_amount,left_amount,action,create_time,remark;$lists

飞腾X100芯片GPU状态查询

本文档对在linux系统下查看X100芯片GPU状态信息进行说明,可以帮助大家了解芯片的实时工作状态。 板子系统信息: # cat /etc/os-release NAME="Kylin" VERSION="银河麒麟桌面操作系统V10 (SP1)" VERSION_US="Kylin Linux Desktop V10 (SP1)" I…

模块化RAG:RAG新范式,像乐高一样搭建 万字长文

1. RAG系统的发展历史与问题 RAG&#xff08;检索增强生成&#xff09;通过访问外部知识库&#xff0c;检索增强生成&#xff08;RAG&#xff09;为 LLMs 提供了关键的上下文信息&#xff0c;极大地提升了其在知识密集型任务上的表现。RAG 作为一种增强手段&#xff0c;已在众…

vue3中使用i18n实现中英文切换,引入封装+全局切换

目录 1.安装 2.引入 3.页面中使用 4.切换语言 前言 名称由来:全称是 internationalization,插件名取了首字母 i 和尾字母 n,中间一共有 18 个字母,所以组合起来就叫 i18n。 作用:通过手动配置多种语言的翻译,且可快速切换。 正文开始↓ 1.安装 npm install vue-i18…

ADAS-GPM

文章目录 AbstractIntroductionmain contribution Related work特征融合上下文信息和注意力机制超分辨率锚框分配 MethodExperiment link Abstract 微小目标检测最近的一个趋势是引入更细粒度的标签分配策略&#xff0c;为分类和回归提供有希望的监督信息。然而&#xff0c;以…

RN环境遇到的问题

空闲学习&#xff0c;记录一下遇到一些问题 RN中文网 问题1&#xff1a;npm error code CERT_HAS_EXPIRED 原因是本地 证书过期解决办法参考 npx react-native init testProject报错&#xff1a; npm error code CERT_HAS_EXPIRED npm error errno CERT_HAS_EXPIRED npm er…

20240806吃干榨尽飞凌OK3588-C的8+64的核心板的eMMC存储空间resize2fs

20240806吃干榨尽飞凌OK3588-C的864的核心板的eMMC存储空间 2024/8/6 11:25 缘起&#xff0c;使用了飞凌OK3588-C的864的核心板&#xff0c;但是默认的LINUX R4版本的SDK编译的IMG固件&#xff0c;刷机之后貌似默认只使用了32GB&#xff1f;的eMCC空间。 联系飞凌提供了resize2…

【ML】为什么要做batch normlization,怎么做batch normlization

为什么要做batch normlization&#xff0c;怎么做batch normlization 1. batch normlization1.1 批量归一化是什么&#xff1a;1.2 为什么要做批量归一化&#xff1a; 2. feature normalization2.1 特征归一化是什么&#xff1a;2.2 为什么要做特征归一化&#xff1a; 3. batc…

Linux中apache服务安装与mysql安装

目录 一、apache安装 二、MySQL安装 一、apache安装 准备环境&#xff1a;一台虚拟机、三个安装包&#xff08;apr-1.6.2.tar.gz、apr-util-1.6.0.tar.gz、httpd-2.4.29.tar.bz2) 安装过程&#xff1a; tar xf apr-1.6.2.tar.gz tar xf apr-util-1.6.0.tar.gz tar xf http…

怎么限制电脑不能打开某个网页或网站(四个方法你可一定要学会)

老板&#xff1a;我公司的员工真的很让人头疼。 朋友&#xff1a;怎么了&#xff1f; 老板&#xff1a;我一不在就有人偷偷打开某些违法网站&#xff0c;画面不可描述啊&#xff01; 朋友&#xff1a;难道你还不知道可以禁止员工打开某个网站&#xff1f; 老板&#xff1a;…