iText7高级教程之html2pdf——5.自定义标签和CSS应用

news2025/10/27 3:24:20

  在本章中,我们将更改pdfHTML插件的两个最重要的内部机制。

  • 我们将覆盖将HTML标签与iText对象匹配的默认功能,更具体地说是DefaultTagWorkerFactory机制,以及
  • 我们将覆盖将CSS样式与iText样式相匹配的默认功能,更具体地说是DefaultCssApplierFactory机制。

  其中一些示例将是很人为化的,但通过这些案例,我们可以更好地了解pdfHTML的内部工作原理。

1. 更改标签的行为

  到目前为止,我们始终隐式使用DefaultTagWorkerFactory类。这是一个实现ITagWorkerFactory接口的类。此工厂接口中只有一个方法:getTagWorker(IElementNode tag,ProcessorContext context)/getTagWorker(IElementNode tag,ProcessorContext context)。方法参数:

  • IElementNode对象可以为你提供有关正在处理的标签的信息(例如标签的名称);
  • ProcessorContext可以让你访问PdfDocument和其他几个转换器属性。

   getTagWorker()/GetTagWorker()方法返回一个ITagWorker实例。

  DefaultTagWorkerFactory实现getTagWorker()/GetTagWorker()方法,并使用DefaultTagWorkerMapping对象将标签的名称映射到ITagWorker实例。此映射存储在TagProcessorMapping实例中。

  以下是标签worker默认映射的一些示例:

  • <p>-标签映射到PTagWorker类,
  • <span>-标签映射到SpanTagWorker类,
  • <a>-标签映射到ATagWorker类,这个类继承SpanTagWorker类,
  • <b>-标签和<i>-标签也映射到SpanTagWorker类。这些标签被认为是特殊类型的<span>-标签。
  • …等等

  PTagWorkerSpanTagWorkerATagWorker等都实现了ITagWorker接口,更具体地说,它们实现了以下四种方法:

  1. processContent()/ProcessContent()——处理元素的打开和关闭标签中存在的任何文本内容,
  2. processTagChild()/ProcessTagChild()——处理嵌套在元素的打开和关闭标签中的其他标签,
  3. processEnd()/ProcessEnd()——包含在处理完所有其他内容后执行的代码,以及
  4. getElementResult()/GetElementResult()——可用于取回最终结果,即IPropertyContainer实例。

  这些标签worker类负责创建和填充iText对象。例如:PTagWorker类有一个Paragraph对象作为成员变量。当遇到<p>标签时,将执行以下步骤:

  1. Paragraph成员变量的实例在PTagWorker对象的构造函数中创建,
  2. 内容在processContent()/ProcessContent()processTagChild()/ProcessTagChild()方法中收集,
  3. ParagraphprocessEnd()/ProcessEnd()方法中最终确定下来,并且
  4. getElementResult()/GetElementResult()方法返回最终确定的Paragraph

  在第一个例子中,我们将从第2章(2_inline_css.html文件)中选取一个示例,但我们将以这样的方式更改标签worker工厂,即将<a>-标签视为<span>标签。图5.1显示了生成的PDF。

图5.1 带有不再是链接的链接的PDF

图5.1 带有不再是链接的链接的PDF

  这个文档看起来与我们在第2章中得到的结果完全相同,但当我们尝试在实际的PDF中单击IMDB链接时,什么都没有发生。HTML中的链接不再是PDF中的链接,因为我们使用ConverterProperties的方法更改了setTagWorkerFactory()/SetTagWorkerFactory()默认的标签worker工厂。代码如下:

public void createPdf(String src, String dest) throws IOException {
    ConverterProperties converterProperties = new ConverterProperties();
    converterProperties.setTagWorkerFactory(
        new DefaultTagWorkerFactory() {
            @Override
            public ITagWorker getCustomTagWorker(
                IElementNode tag, ProcessorContext context) {
                    if ("a".equalsIgnoreCase(tag.name()) ) {
                        return new SpanTagWorker(tag, context);
                    }
                    return null;
                }
            } );
    HtmlConverter.convertToPdf(new File(src), new File(dest), converterProperties);
}

  当然,我们可以创建ITagWorkerFactory接口的全新实现,但这将是一项巨大的工作。我们希望尽可能多地利用现有的pdfHTML功能,以便能够正确地呈现<div><h1>等标签。

  我们可以通过重用DefaultTagWorkerFactory中已经存在的功能来做到这一点。DefaultTagWorkerFactory有一个名为getCustomTagWorker()/GetCustomTagWorkers()的方法,该方法始终返回空值null。此方法始终是DefaultTagWorkerFactorygetTagWorker()/GetTagWorker()的实现方法中调用的第一个方法。

  • 如果getCustomTagWorker()/GetCustomTagWorkers()方法返回null(除非重写该方法,否则始终是这种情况),则使用默认的标签工作者映射。例如:如果遇到<a>-标签,则返回ATagWorker实例。
  • 如果getCustomTagWorker()/GetCustomTagWorkers()方法未返回null(在我们上面的“重写”版本中可能是这种情况),则默认映射将被忽略。例如:如果在我们的示例中遇到<a>-标签,则返回SpanTagWorker实例,而不是期望的ATagWorker

  我们还没有覆盖任何CSS appliers,这解释了为什么IMDB这个词被加下划线并呈现为蓝色,但由于我们从功能和结构的角度将<a>-标签视为<span>-标签,因此没有添加任何链接。编写此示例是为了解释pdfHTML的内部工作原理。我们在第一个例子中所做的很容易被认为是引入了一个bug。

  但这并不意味着没有任何有用的用例。例如,我们可以扩展DefaultTagWorkerFactory以支持自定义标签。

2. 引入自定义标签

  假设我们想向不同的人发送邀请函。我们用HTML创建了这封邀请函(请见invitation.html),但我们引入了两个非真实的自定义标签,非现有的HTML语法:<name><date>,html文件内容如下:

<html>
    <head>
        <title>Invitation to SXSW 2018</title>
    </head>
    <body>
        <u><b>Re: Invitation</b></u>
        <br>
        <p>Dear <name>SXSW visitor</name>,
        we hope you had a great SXSW film festival experience last year.
        And we would like to invite you to the next edition of SXSW Film
        that takes place from March 9 until March 17, 2018.</p>
        <p>Sincerely,<br>
        The SXSW crew<br>
        <date>August 4, 2017</date></p>
    </body>
</html>

  我们使用createPdf()/CreatePdf()方法把这个html文件转换成PDF:

public void createPdf(String src, String dest) throws IOException {
    HtmlConverter.convertToPdf(new File(src), new File(dest));
}

  如果我们查看图5.2,我们会看到“SXSW visitor”和“August 4, 2017”作为常规文本。与其他内容没有明显区别。未知标签被视为普通的<span>标签。

图5.2 自定义标签被视为标签

图5.2 自定义标签被视为<span>标签

  让我们来看接下来的例子,在main()方法找那个,我们使用同一个HTML,但是生产不同三个PDF文件,代码如下:

String[] names = {"Bruno Lowagie", "Ingeborg Willaert", "John Doe"};
int counter = 1;
for (String name : names) {
    app.createPdf(name, SRC, String.format(DEST, counter++));
}

  在这个示例的createPdf()/CreatePdf()方法中,我们根据DefaultTagWorkerFactory上创建一个变体,该变体为标签<name><date>返回SpanTagWorker。并且我们重写这些SpanTagWorker实例的processContent()/ProcessContent()方法,从而忽略实际内容(content变量)。相反,name或今天的日期作为内容传递。代码如下:

public void createPdf(String name, String src, String dest) throws IOException {
    SimpleDateFormat sdf = new SimpleDateFormat("MMMM d, yyyy", Locale.ENGLISH);
    ConverterProperties converterProperties = new ConverterProperties();
    converterProperties.setTagWorkerFactory(
        new DefaultTagWorkerFactory() {
            @Override
            public ITagWorker getCustomTagWorker(
                IElementNode tag, ProcessorContext context) {
                if ("name".equalsIgnoreCase(tag.name()) ) {
                    return new SpanTagWorker(tag, context) {
                        @Override
                        public boolean processContent(
                            String content, ProcessorContext context) {
                            return super.processContent(name, context);
                        }
                    };
                }
                else if ("date".equalsIgnoreCase(tag.name()) ) {
                    return new SpanTagWorker(tag, context) {
                        @Override
                        public boolean processContent(
                            String content, ProcessorContext context) {
                            return super.processContent(
                                sdf.format(new Date()), context);
                        }
                    };
                }
                return null;
            }
        } );
    HtmlConverter.convertToPdf(new File(src), new File(dest), converterProperties);
}}

  此代码将导致默认内容(“SXSW visitor”和“August 4, 2017”)替换为特定名称和今天的日期。生成的PDF如图5.3所示。

图5.3 自定义标签用于插入自定义数据

图5.3 自定义标签用于插入自定义数据

  在某些情况下,不可能重用现有的ITagWorker的任意实现。我们必须创建自己的标签worker程序。

3. 创建你自己的ITagWorker实现

  假设我们想要创建一个显示QR二维码的PDF文档,例如基于qrcode.html文件:

<html>
<head>
    <meta charset="UTF-8">
    <title>QRCode Example</title>
    <link rel="stylesheet" type="text/css" href="css/qrcode.css"/>
</head>
<body>
    <h1>SXXpress codes</h1>
    <p>Bruno Lowagie has a South by Express pass for the following movies:
    <p>Colossal</p>
    <qr charset="Cp437" errorcorrection="Q">
    Film: Colossal; Date: Friday, March 10; Time: 6:15 PM; Place: Alamo Lamar D
    </qr>
    <p>Mr. Roosevelt</p>
    <qr charset="Cp437" errorcorrection="L">
    Film: Mr. Roosevelt; Date: Sunday, March 12; Time: 2:15 PM; Place: Paramount Theatre
    </qr>
</body>
</html>

  HTML中没有<qr>-标签,因此当我们在浏览器中打开此HTML文件时,我们只能看到文本。然而,我们可以使用此HTML文件创建带有QR码而不是文本的PDF文档。如图5.4

图5.4 从HTML创建QR二维码<

图5.4 从HTML创建QR二维码

  HTML文件中的文本有一个红色边框,因为这就是我们如何在qrcode.css文件中定义<qr>-标签的样式:

qr {
    border:solid 1px red;
    height:200px;
    width:200px;
}

  这次,我们创建了一个继承扩展DefaultTagWorkerFactory类的QRCodeTagWorkerFactory

class QRCodeTagWorkerFactory extends DefaultTagWorkerFactory {
    @Override
    public ITagWorker getCustomTagWorker(IElementNode tag, ProcessorContext context) {
        if(tag.name().equals("qr")){
            return new QRCodeTagWorker(tag, context);
        }
        return null;
    }
}

  如果遇到<qr>-标签,我们将其映射到QRCodeTagWorker。此QRCodeTagWorker需要实现ITagWorker接口:

static class QRCodeTagWorker implements ITagWorker {
    private static String[] allowedErrorCorrection =
        {"L","M","Q","H"};
    private static String[] allowedCharset =
        {"Cp437","Shift_JIS","ISO-8859-1","ISO-8859-16"};
    private BarcodeQRCode qrCode;
    private Image qrCodeAsImage;
 
    public QRCodeTagWorker(IElementNode element, ProcessorContext context){
        Map<EncodeHintType, Object> hints = new HashMap<>();
        String charset = element.getAttribute("charset");
        if(checkCharacterSet(charset)){
            hints.put(EncodeHintType.CHARACTER_SET, charset);
        }
        String errorCorrection = element.getAttribute("errorcorrection");
        if(checkErrorCorrectionAllowed(errorCorrection)){
            ErrorCorrectionLevel errorCorrectionLevel =
                getErrorCorrectionLevel(errorCorrection);
            hints.put(EncodeHintType.ERROR_CORRECTION, errorCorrectionLevel);
        }
        qrCode = new BarcodeQRCode("placeholder",hints);
    }
 
    @Override
    public boolean processContent(String content, ProcessorContext context) {
        qrCode.setCode(content);
        return true;
    }
 
    @Override
    public boolean processTagChild(
        ITagWorker childTagWorker, ProcessorContext context) {
        return false;
    }
 
    @Override
    public void processEnd(IElementNode element, ProcessorContext context) {
        qrCodeAsImage = new Image(qrCode.createFormXObject(context.getPdfDocument()));
    }
 
    @Override
    public IPropertyContainer getElementResult() {
        return qrCodeAsImage;
    }
 
    private static boolean checkErrorCorrectionAllowed(String toCheck){
        for(int i = 0; i<allowedErrorCorrection.length;i++){
            if(toCheck.toUpperCase().equals(allowedErrorCorrection[i])){
                return true;
            }
        }
        return false;
    }
 
    private static boolean checkCharacterSet(String toCheck){
        for(int i = 0; i<allowedCharset.length;i++){
            if(toCheck.equals(allowedCharset[i])){
                return true;
            }
        }
        return false;
    }
 
    private static ErrorCorrectionLevel getErrorCorrectionLevel(String level){
        switch(level) {
            case "L":
                return ErrorCorrectionLevel.L;
            case "M":
                return ErrorCorrectionLevel.M;
            case "Q":
                return ErrorCorrectionLevel.Q;
            case "H":
                return ErrorCorrectionLevel.H;
        }
        return null;
    }
}

  静态字符串数组是可以传递给BarcodeQRCode类构造函数的提示的可能值。如果你看一下qrcode.htm文件,可以看到我们使用属性charseterrorcorrection传递这些值。

  我们看到两个成员变量,一个是BarcodeQRCode实例;另一个是Image实例。BarcodeQRCode对象是一个低级对象,它知道如何绘制QR码,但最终,我们需要一个结果,它是IPropertyContainer接口的实例。为了得到这样的结果,我们将二维码包装在Image对象中。请放心:此操作不会将QR码更改为光栅/点位图像;它不会降低QR码的分辨率和易读性。Image类完全能够存储矢量图像,例如使用iText的二维码功能创建的二维码。

  在这之后,我们有了自定义QR码标签worker的构造函数。在这个构造函数中,我们首先创建一个变量名为hintsMap/Dictionary。我们从<qr>-标签的属性中获取这些提示的值。也就是charseterrorcorrection 。当然我们只接受允许的值,我们使用checkCharacterSet()/CheckCharacterSet()checkErrorCorrectionAllowed()/CheckErrorCorrectionallowed()方法检查这些值。然后使用getErrorCorrectionlevel()方法将errorcorrection登记属性转换为ErrorCorrectionLevel 。处理完属性后,我们将创建一个BarcodeQRCode实例。因为我们还不知道二维码的内容,所以我们使用“placeholder”作为代码的值。

  我们用processContent()/ProcessContent()方法中的实际内容替换这个“placeholder”。同时我们不希望<qr>-标签有任何嵌套的标签,因此processTagChild()/ProcessTagChild()方法可以简单地返回false。在过程结束时,在processEnd()方法中,我们将qrCode对象包装在Image对象中。与BarcodeQRCode对象不同,Image类实现IPropertyContainer接口。我们在getElementResult()/GetElementResult()方法中返回这个IPropertyContainer对象。

  我们已经成功地实现了ITagWorker接口的四种方法,现在我们可以在自定义QRCodeTagWorkerFactory中使用这个QRCodeTagWorker

  将此自定义QRCodeTagWorkerFactory用作createPdf()/CreatePdf()方法中的ConverterProperties 之一:

public void createPdf(String src, String dest) throws IOException {
    ConverterProperties properties = new ConverterProperties();
    properties
        .setCssApplierFactory(new QRCodeTagCssApplierFactory())
        .setTagWorkerFactory(new QRCodeTagWorkerFactory());
    HtmlConverter.convertToPdf(new File(src), new File(dest), properties);
}

  从这段代码中可以看出,我们使用setTagWorkerFactory()/SetTagWorkerFactory()方法来扩展标签处理机制,但我们也使用setCsApplierFactory()/SetCsApplierFactory()来扩展CSS功能的处理。

  这在本例中是必要的,因为我们使用CSS为<qr>-标签定义了边框颜色、宽度和高度。然而,由于<qr>是一个自定义标签,iText不知道遇到此类标签时要使用ICssApplier的哪个实现。我们可以通过重写DefaultCsApplierFactory类的getCustomCsApprier()/GetCustomCsApp()方法轻松解决此问题:

class QRCodeTagCssApplierFactory extends DefaultCssApplierFactory {
    @Override
    public ICssApplier getCustomCssApplier(IElementNode tag) {
        if (tag.name().equals("qr")) {
            return new BlockCssApplier();
        }
        return null;
    }
}

  在这段代码中,我们告诉iText,每当遇到<qr>-标签时,我们都需要将该标签视为块元素。我们只需重用用于块元素(如<div><p><blockquote>等)的BlockCsApplier。你可以查看DefaultTagCsApplierMapping的源代码,以获得默认ICssApplier映射的完整概述。

  在接下来的几个示例中,我们将创建自定义CSS appliers。

4. 创建自定义css applier

  在上一个示例中,我们扩展了DefaultCssApplierFactory,为<qr>-标签添加了BlockCssApplier。在下一个示例中,我们将创建自己的ICssApplier实现。

  在这个相当人为的示例中,我们将使用第3章中的sxsw.html文件,并将覆盖所有<div>-元素的CSS applier。我们将忽略这些元素的所有样式,除了background。如果为<div>-元素定义了背景色,我们将用灰色值(#dddddd)替换实际值。见图5.5

图5.5 -元素的现有背景变为灰色

图5.5 <div>-元素的现有背景变为灰色

  ICssApplier接口有一个名为apply()/apply()的方法。在这个示例中,我们实现了如下方法:

class GrayBackgroundBlockCssApplier implements ICssApplier {
    public void apply(ProcessorContext context,
        IStylesContainer stylesContainer, ITagWorker tagWorker){
        Map<String, String> cssProps = stylesContainer.getStyles();
        IPropertyContainer container = tagWorker.getElementResult();
        if (container != null && cssProps.containsKey(CssConstants.BACKGROUND_COLOR)) {
            cssProps.put(CssConstants.BACKGROUND_COLOR, "#dddddd");
            BackgroundApplierUtil.applyBackground(cssProps, context, container);
        }
    }
}

  如果有背景色,我们用#dddddd替换该颜色,然后使用BackgroundApplierUtil类将背景应用于container你仍然可以在生成的PDF中看到带有彩色背景的元素。这是因为这些背景是在<h2><li>元素的上下文中定义的,而我们的自定义ICssApplier仅用于<div>元素。

  在实现这种功能时要非常小心。在这种情况下,我们完全忽略了任何其他可能适用的CSS属性。扩展现有的ICssApplier实现要好得多。

  在下一个示例中,我们将使用一些“Dutch CSS”来定义颜色。由于没有“Dutch CSS”,我们将扩展DefaultCssApplierFactory以支持这种虚构的CSS功能。

5. 实现自己的自定义CSS

  dutch_css.html文件是我们在第2章中使用的文件的变体,但它有一些特殊之处,它使用了Dutch(荷兰语)版本的CSS:

<html>
    <head>
        <title>Colossal</title>
        <meta name="description" content="Gloria is an out-of-work party girl..." />
    </head>
    <body>
        <img src="img/colossal.jpg" style="width: 120px;float: right" />
        <h1 style="achtergrond: rood; kleur: wit;">Colossal (2016)</h1>
        <div style="font-style: italic; kleur: blauw;">Directed by Nacho Vigalondo</div>
        <div style="kleur: groen;">
        Gloria is an out-of-work party girl forced to leave her life in New York City,
        and move back home. When reports surface that a giant creature is
        destroying Seoul, she gradually comes to the realization that she is
        somehow connected to this phenomenon.
        </div>
        <div style="font-size: 0.8em">Read more about this movie on
        <a href="www.imdb.com/title/tt4680182">IMDB</a></div>
    </body>
</html>

  你看到我们如何定义这个HTML文件中的颜色了吗?我们使用了achtergrond代替backgroundkleur代替color,我们使用了颜色witroodgroenblauw。显然,这在浏览器中不起作用,但我们可以在将HTML转换为PDF时使其发挥作用。见图5.6

图5.6 将HTML转换为PDF时使Dutch CSS生效

图5.6 将HTML转换为PDF时使Dutch CSS生效

  为了实现这一点,我们将荷兰语颜色名称映射为英文颜色名称。代码如下:

public static final Map<String, String> KLEUR = new HashMap<String, String>();
static {
    KLEUR.put("wit", "white");
    KLEUR.put("zwart", "black");
    KLEUR.put("rood", "red");
    KLEUR.put("groen", "green");
    KLEUR.put("blauw", "blue");
}

  我们同样继承BlockCssApplier类:

class DutchColorCssApplier extends BlockCssApplier {
    @Override
    public void apply(ProcessorContext context,
        IStylesContainer stylesContainer, ITagWorker tagWorker){
        Map<String, String> cssStyles = stylesContainer.getStyles();
        if(cssStyles.containsKey("kleur")){
            cssStyles.put(CssConstants.COLOR,
                KLEUR.get(cssStyles.get("kleur")));
            stylesContainer.setStyles(cssStyles);
        }
        if(cssStyles.containsKey("achtergrond")){
            cssStyles.put(CssConstants.BACKGROUND_COLOR,
                KLEUR.get(cssStyles.get("achtergrond")));
            stylesContainer.setStyles(cssStyles);
        }
        super.apply(context, stylesContainer,tagWorker);
    }
}

  每当我们遇到Dutch CSS属性“kleur”时,我们都会将其值转换为英文,并将该值设置为color属性。每当我们遇到Dutch CSS属性achtergrond时,我们将其值转换为英语,并将此值设置为background属性。对于所有其他CSS属性,我们依赖于它们在BlockCsApplier类中的实现。

  我们将对每个<h1>-和<div>-标签使用DutchColorCsApplier

public void createPdf(String src, String dest) throws IOException {
    ConverterProperties converterProperties = new ConverterProperties();
    converterProperties.setCssApplierFactory(new DefaultCssApplierFactory() {
        ICssApplier dutchCssColor = new DutchColorCssApplier();
        @Override
        public ICssApplier getCustomCssApplier(IElementNode tag) {
            if(tag.name().equals(TagConstants.H1)
                || tag.name().equals(TagConstants.DIV)){
                return dutchCssColor;
            }
            return null;
        }
    });
    HtmlConverter.convertToPdf(new File(src), new File(dest), converterProperties);
}

  当然这种制作法具体在上面用例里面有用需要你自己去考虑,但望这些示例能够提供有关pdfHTML内部工作的一些见解,同时证明pdfHTML插件是高度可扩展的。使用本章中描述的机制,你可以根据自己的需要和需求调整pdfHTML。

6. 总结

  在本章中,我们通过改变标签和CSS的解释方式来改变了pdfHTML插件的默认核心功能。我们(故意)引入了一个bug,使<a>-标签的行为就像是一个普通的<span>-标签。我们引入了自定义标签,来用作名称、日期甚至QR码的占位符。同时降级了<div>-标签的CSS属性,以便忽略除背景之外的所有CSS属性。最后,我们引入了Dutch CSS来定义颜色,并使这种CSS在HTML到PDF转换过程中发挥作用。

  在下一章中,我们将讨论本教程中早该讨论的主题:字体。到目前为止,我们只使用了Helvetica和FreeSans等字体,但我们可以使用哪些其他字体?我们将在下一章中回答这个问题。

iText7高级教程之html2pdf教程源码下载

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

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

相关文章

【Python百日进阶-WEB开发-冲進Flask】Day183 - Flask数据库ORM基础、增加

文章目录一、day03项目环境和结构搭建1.1 flask-script1.1.1 flask-script是干什么的&#xff1f;1.1.2 flask-script安装1.1.3 flask-script的使用1.1.3.1 创建Manager实例1.1.3.2 初始化实例出错与解决1.1.4 终端启动1.1.4.1 查看runserver参数1.1.5 自定义添加manager命令1.…

ARM-A架构入门基础(二)异常处理

14天学习训练营导师课程&#xff1a;周贺贺《ARMv8/ARMv9架构-快速入门》 1. 异常处理种类 1.1 中断 在ARM中&#xff0c;FIQ的优先级要高于IRQ&#xff0c;在SOC内部会有一个中断控制器负责中断优先级调度&#xff0c;然后发送中断信号给处理器。中断属于异步模式的异常。 …

Python定时打开世界杯直播,还有小姐姐语音提醒哦~不错过每一场世界杯比赛

前言 卡塔尔世界杯今晚0点就要开幕了&#xff0c;为了防止大家沉迷工作&#xff0c;忘记看球&#xff0c;小编用50行Python代码写了一个定时提醒你看球的小程序&#xff0c;还有小姐姐语音提醒哟~&#x1f387; &#xff08;文末送读者福利&#xff09; 1、代码说明 获取上…

Redmine 插件 实现富文本编辑,可插入表格和

基本介绍 Redmine 插件将“所见即所得"的UEditor富文本编辑器完美移植到了Redmine。UEditor具有界面美观、功能丰富、注重用户体验的特点&#xff0c;使用方法比CKeditor更为简便。 该插件在redmine现有的文本编辑模式上添加WYSIWYG所见即所得编辑功能。 Redmine UEditor主…

功能测试如何进阶自动化测试?5个步骤带你成功进阶...

手动测试人员应该权衡测试自动化相对于手动测试的好处&#xff0c;并且即可开始行动。下面我介绍一下从手动测试到自动化测试转换的5步指南。 步骤1: 查找合适的自动化测试用例 测试自动化在重复测试中发挥着极其重要的作用。可以在下表中找到最适合自动化的测试类型列表。 测…

【学习笔记20】JavaScript数据类型之间的区别

一、数据类型 基本数据类型复杂数据类型 (function; object; array)二、存储的区别 基本数据类型: 存储在栈内存中, 变量内部就是实际的值引用数据类型: 变量存储在栈内存中, 变量内部存储的是指向堆内存的地址(对象实际的值, 存储在堆内存中)三、赋值的区别 1. 基本数据类型: …

Linux零基础从入门到精通,必学的55个指令合集【下篇】

Linux学习笔记 资料下载&#xff1a; 链接: https://pan.baidu.com/s/1UvwkJaEJO7W3sU5qkCgKzA?pwdfe2f提取码: fe2f 本篇文章主要适用0基础的读者&#xff0c;内容会比较通俗易懂&#xff0c;也会有详细的图解教程&#xff0c;以及运行后的返回结果。我本人在系统性的学习…

Rsync已过时?替代文件同步方式了解一下

随着企业结构分散化的不断扩大&#xff0c;企业内部和企业间的信息互动更加频繁。越来越多的企业要求内部各种业务数据在服务器、数据中心甚至云上能够有实时的同步留存。所以&#xff0c;企业需要文件同步软件&#xff0c;通过在两个或更多设备之间同步数据并自动更新更改来确…

零时科技 || 分布式资本创始人4200万美金资产被盗分析及追踪工作

事件背景 2022年11月23日&#xff0c;分布式资本创始人沈波发推文称&#xff0c;价值4200万美元的个人钱包资产被盗&#xff0c;其中包含 3800 万枚 USDC和1606 枚 ETH&#xff0c;在纽约时间 11 月 10 日凌晨被盗。被盗资产为个人资金&#xff0c;与分布式相关基金无关。目前…

【学习笔记16】JavaScript函数封装习题

笔记初发 1、书写一个函数, 求任意两个数字的和, 把结果以弹窗的形式展示 书写一个函数需要参数吗?            -->需要需要几个参数?         —>两个函数要做什么?         -->求和然后弹窗的形式展示    -->alert(弹窗展示的值…

Area of a circle

In geometry, the area enclosed by a circle of radius r is πr2. Here the Greek letter π represents the constant ratio of the circumference of any circle to its diameter, approximately equal to 3.14159. One method of deriving this formula, which originate…

【矩阵论】4. 矩阵运算——张量积

4.2 张量积 4.2.1 定义 设A(aij)mn,B(bij)pq,则称如下分块矩阵(a11Ba12B⋯a1nBa21Ba22B⋯a2nB⋮⋮⋱⋮an1Ban2⋯ann)为A与B的张量积记作A⊗B(aijB)mpnq\begin{aligned} &设A(a_{ij})_{m\times n},B(b_{ij})_{p\times q},则称如下分块矩阵\left( \begin{matrix} a_{11}B&am…

【Dense Res2net:两个非局部注意模型:IVIF】

Res2Fusion: Infrared and Visible Image Fusion Based on Dense Res2net and Double Nonlocal Attention Models &#xff08;Res2Fusion: 基于密集Res2net和双非局部注意模型的红外和可见光图像融合&#xff09; 红外和可见光图像融合旨在生成具有出色场景表示和更好视觉感…

Zabbix最新6.2安装及使用!

zabbix官网 Zabbix 是由 Alexei Vladishev 创建&#xff0c;目前是由 Zabbix SIA 在持续开发和提供支持。 Zabbix 是一款能够监控众多网络参数和服务器的健康度和完整性的软件。Zabbix 使用灵活的通知机制&#xff0c;允许用户为几乎任何事件配置基于邮件的警报。这样可以快速…

数字赋能之学生返乡报备登记管理系统

年关将至&#xff0c;不少高校即将开启寒假模式了。对于不少地方而言&#xff0c;第一批返乡高潮快要到来了。当前&#xff0c;国内疫情防控形势异常严峻&#xff0c;且多地发生外返大学生初筛阳性&#xff0c;为切实保障广大返乡大学生及家人、朋友的身体健康&#xff0c;各个…

MYSQL 基本操作 (2)

分组查询和聚合查询&#xff1a; group by (列名) 根据查询的结果来进行分组&#xff0c;值相同的记录分到一组&#xff0c;然后针对每一组进行聚合。 目前表中&#xff0c;对容易一行进行分组操作都会去除重复名字项&#xff0c;同时将重复项打包进改组&#xff0c;例如&…

TingsBoard源码解析-登录认证-OAuth2认证

配置类&#xff1a;ThingsboardSecurityConfiguration 用户名密码登录 用户名密码登录请求URL: /api/auth/login 配置中发现&#xff1a;在默认的用户名密码认证之前添加了认证拦截器【RestLoginProcessingFilter】&#xff0c;而该拦截器拦截将拦截用户名密码登录请求&#x…

项目管理证书 PMP 的含金量高吗?

PMP 含金量&#xff0c;PMP有没有用&#xff0c;这类问题一直是大家关注的重点&#xff0c;知乎上几个相关问题热度也一直很高。 作为有 7 年项目经验的 PMP 持证者&#xff0c;我要跟大家说句实话&#xff1a; PMP 最基础的是项目管理领域的一个资格认证证书&#xff0c;相当…

FastDFS分布式文件系统

FastDFS分布式文件系统 FastDFS是由国人开发的针对中小文件存储的轻量级分布式文件系统&#xff0c;使用C语言进行开发&#xff0c;效率高、跨平台&#xff0c;可以在类UNIX系统上很好运行。整体设计以简单高效为原则&#xff0c;具有冗余备份、负载均衡、在线扩容等性能。 F…

开源生态企业反哺GitLink确实开源创新服务--DevOps引擎合作

日前&#xff0c;建木正式入驻到GitLink引擎模块下。 建木是DevOps领域的小能手&#xff0c;而GitLink又致力于提供强大的开源基础设施&#xff0c;双方可谓一拍即合&#xff0c;强强联手为开发者提供更愉悦、更轻松的研发体验&#xff01; GitLink&#xff08;确实开源&#…