欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java使用FreeMarker實(shí)現(xiàn)動(dòng)態(tài)生成Word文檔

 更新時(shí)間:2025年05月13日 09:21:58   作者:熊文豪  
在電纜行業(yè),生成供貨清單是一項(xiàng)常見(jiàn)但繁瑣的任務(wù),這篇文章將介紹如何使用現(xiàn)代Java技術(shù)棧自動(dòng)化這一過(guò)程,文中的示例代碼簡(jiǎn)潔易懂,感興趣的小伙伴可以參考一下

引言

在電纜行業(yè),生成供貨清單是一項(xiàng)常見(jiàn)但繁瑣的任務(wù)。本教程將介紹如何使用現(xiàn)代Java技術(shù)棧自動(dòng)化這一過(guò)程,大幅提高工作效率和準(zhǔn)確性。我們將使用SpringBoot作為框架,Apache POI處理Word文檔,以及FreeMarker作為模板引擎來(lái)實(shí)現(xiàn)這一功能!

讓我們先了解一下這個(gè)問(wèn)題的背景:

  • 在電纜行業(yè),手動(dòng)創(chuàng)建供貨清單是一個(gè)復(fù)雜且重復(fù)的過(guò)程。
  • 這個(gè)過(guò)程不僅耗時(shí),還容易出錯(cuò),影響工作效率和數(shù)據(jù)準(zhǔn)確性。

為了解決這個(gè)問(wèn)題,我們提出了一個(gè)技術(shù)方案,結(jié)合了以下幾個(gè)關(guān)鍵技術(shù):

  • SpringBoot: 作為我們的主要開(kāi)發(fā)框架
  • Apache POI: 用于生成和操作Word文檔
  • FreeMarker模板引擎: 用于生成Word文件的內(nèi)容

這個(gè)方案的主要優(yōu)勢(shì)包括:

  • 靈活性: 使用FreeMarker模板可以輕松調(diào)整文檔格式,而無(wú)需修改程序代碼。
  • 效率: 自動(dòng)化生成過(guò)程大大減少了人工操作,提高了辦公效率。
  • 準(zhǔn)確性: 自動(dòng)化處理確保了數(shù)據(jù)的準(zhǔn)確性和一致性。
  • 適用性: 特別適合電纜行業(yè)的業(yè)務(wù)需求,生成符合要求的.doc文件。

通過(guò)閱讀這篇博客,您將學(xué)習(xí)如何實(shí)現(xiàn)這個(gè)解決方案,從而幫助您或您的團(tuán)隊(duì)簡(jiǎn)化工作流程,提高生產(chǎn)效率。

效果圖:

項(xiàng)目結(jié)構(gòu)

src/
├── main/
│   ├── java/
│   │   └── com/
│   │       └── pw/
│   │           ├── WordController.java  #負(fù)責(zé)生成測(cè)試數(shù)據(jù)并調(diào)用WordUtil工具類來(lái)生成Word文檔
│   │           └── utils/
│   │               └── WordUtil.java  #這個(gè)工具類封裝了使用FreeMarker生成Word文檔的核心功能
│   └── resources/
│       └── templates/
│           └── template.ftl #模版定義了Word文檔的結(jié)構(gòu)和樣式,使用HTML和CSS來(lái)格式化內(nèi)容

1.WordController類:這個(gè)類是我們應(yīng)用的入口點(diǎn),負(fù)責(zé)生成測(cè)試數(shù)據(jù)并調(diào)用WordUtil來(lái)生成Word文檔。

2.WordUtil類:這個(gè)工具類封裝了使用FreeMarker生成Word文檔的核心邏輯。

3.FreeMarker模版(template.ftl):這個(gè)模版定義了Word文檔的結(jié)構(gòu)和樣式,使用HTML和CSS來(lái)格式化內(nèi)容。

源代碼展示

1.WordController

import com.pw.utils.WordUtil;
 
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
 
public class WordController {
 
    public static void main(String[] args) throws IOException {
        // 指定保存Word文件的目錄
        String filePath = "F:\\Poi2Word\\src\\main\\resources\\output"; // 更改為您希望的目錄
        new WordController().generateWordFile(filePath);
    }
 
    public void generateWordFile(String directory) throws IOException {
        List<Map<String, Object>> listMap = new ArrayList<>();
 
        //測(cè)試數(shù)據(jù)
        addTestData(listMap, "4600025747", "絕緣導(dǎo)線", "AC10kV,JKLYJ,300", 1500, "米", "盤(pán)號(hào):A1");
        addTestData(listMap, "4600025748", "絕緣導(dǎo)線", "AC10kV,JKLGYJ,150/30", 2500, "米", "盤(pán)號(hào):A2");
        addTestData(listMap, "4600025749", "絕緣導(dǎo)線", "AC10kV,JKLGYJ,150/30", 3500, "米", "盤(pán)號(hào):A3");
        addTestData(listMap, "4600025750", "絕緣導(dǎo)線", "AC10kV,JKLGYJ,150/30", 4500, "米", "盤(pán)號(hào):A4");
        addTestData(listMap, "4600025751", "絕緣導(dǎo)線", "AC10kV,JKLGYJ,150/30", 3800, "米", "盤(pán)號(hào):A5");
        addTestData(listMap, "4600025752", "絕緣導(dǎo)線", "AC10kV,JKLYJ,180", 2000, "米", "盤(pán)號(hào):A6");
        addTestData(listMap, "4600025753", "絕緣導(dǎo)線", "AC10kV,JKLYJ,120", 4200, "米", "盤(pán)號(hào):A7");
        addTestData(listMap, "4600025754", "絕緣導(dǎo)線", "AC10kV,JKLYJ,120", 3700, "米", "盤(pán)號(hào):A8");
        addTestData(listMap, "4600025755", "絕緣導(dǎo)線", "AC10kV,JKLYJ,120", 4300, "米", "盤(pán)號(hào):A9");
        addTestData(listMap, "4600025756", "絕緣導(dǎo)線", "AC10kV,JKLGYJ,100/20", 2800, "米", "盤(pán)號(hào):A10");
        addTestData(listMap, "4600025757", "絕緣導(dǎo)線", "AC10kV,JKLGYJ,100/20", 2400, "米", "盤(pán)號(hào):A11");
        addTestData(listMap, "4600025758", "絕緣導(dǎo)線", "AC10kV,JKLGYJ,100/20", 2600, "米", "盤(pán)號(hào):A12");
 
        HashMap<String, Object> map = new HashMap<>();
        map.put("qdList", listMap);  // 添加供貨清單數(shù)據(jù)
        map.put("contacts", "張三");  // 聯(lián)系人
        map.put("contactsPhone", "13988887777");  // 聯(lián)系電話
        map.put("date", "2025年01月18日");  // 日期
        map.put("company", "新電纜科技有限公司");  // 公司名稱
        map.put("customer", "國(guó)網(wǎng)北京市電力公司");  // 客戶
 
        String wordName = "template.ftl"; // FreeMarker模板文件名
        String fileName = "供貨清單" + System.currentTimeMillis() + ".doc"; // 帶時(shí)間戳的文件名
        String name = "name";  // 臨時(shí)文件名
 
        // 確保輸出目錄存在
        File directoryFile = new File(directory);
        if (!directoryFile.exists()) {
            directoryFile.mkdirs();  // 如果目錄不存在則創(chuàng)建
        }
 
        // 生成Word文件
        WordUtil.exportMillCertificateWord(directory, map, wordName, fileName, name);
 
        System.out.println("文件成功生成在:" + directory + fileName);
    }
 
    private void addTestData(List<Map<String, Object>> listMap, String danhao, String name, String model, int num, String unit, String remark) {
        Map<String, Object> item = new HashMap<>();
        item.put("serNo", listMap.size() + 1);  // 序號(hào)
        item.put("danhao", danhao);  // 單號(hào)
        item.put("name", name);  // 產(chǎn)品名稱
        item.put("model", model);  // 規(guī)格型號(hào)
        item.put("num", String.valueOf(num));  // 數(shù)量,轉(zhuǎn)換為字符串
        item.put("unit", unit);  // 單位
        item.put("remark", remark);  // 備注
        listMap.add(item);  // 將數(shù)據(jù)添加到列表
    }
}

2.WordUtil工具類

package com.pw.utils;
 
import freemarker.template.Configuration;
import freemarker.template.Template;
 
import java.io.*;
import java.util.Map;
 
public class WordUtil {
    private static Configuration configuration = null;
 
    // 模板文件夾路徑
    private static final String templateFolder = WordUtil.class.getResource("/templates").getPath();
 
    static {
        configuration = new Configuration();
        configuration.setDefaultEncoding("utf-8");
        try {
            System.out.println(templateFolder);
            configuration.setDirectoryForTemplateLoading(new File(templateFolder));  // 設(shè)置模板加載路徑
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
 
    private WordUtil() {
        throw new AssertionError();  // 防止實(shí)例化
    }
 
    /**
     * 導(dǎo)出Word文檔
     * @param map Word文檔中參數(shù)
     * @param wordName 模板的名字,例如xxx.ftl
     * @param fileName Word文件的名字 格式為:"xxxx.doc"
     * @param outputDirectory 輸出文件的目錄路徑
     * @param name 臨時(shí)的文件夾名稱,作為Word文件生成的標(biāo)識(shí)
     * @throws IOException
     */
    public static void exportMillCertificateWord(String outputDirectory, Map map, String wordName, String fileName, String name) throws IOException {
        Template freemarkerTemplate = configuration.getTemplate(wordName);  // 獲取模板文件
        File file = null;
        try {
            // 調(diào)用工具類的createDoc方法生成Word文檔
            file = createDoc(map, freemarkerTemplate, name);
 
            // 確保輸出目錄存在
            File dir = new File(outputDirectory);
            if (!dir.exists()) {
                dir.mkdirs();  // 如果目錄不存在則創(chuàng)建
            }
 
            // 定義完整的文件路徑
            File outputFile = new File(outputDirectory, fileName);
 
            // 重命名并移動(dòng)文件到指定目錄
            file.renameTo(outputFile);
 
            System.out.println("文件成功生成在: " + outputFile.getAbsolutePath());
        } finally {
            if (file != null && file.exists()) {
                file.delete();  // 刪除臨時(shí)文件
            }
        }
    }
 
    private static File createDoc(Map<?, ?> dataMap, Template template, String name) {
        File f = new File(name);
        try {
            // 使用OutputStreamWriter來(lái)指定編碼,防止特殊字符出問(wèn)題
            Writer w = new OutputStreamWriter(new FileOutputStream(f), "utf-8");
            template.process(dataMap, w);  // 使用FreeMarker處理模板
            w.close();
        } catch (Exception ex) {
            ex.printStackTrace();
            throw new RuntimeException(ex);
        }
        return f;  // 返回生成的文件
    }
}

3.FreeMarker模版

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>${company}送貨清單</title>
    <style>
        body { font-family: SimSun, serif; }  <!-- 設(shè)置字體 -->
        table { border-collapse: collapse; width: 100%; }  <!-- 設(shè)置表格樣式 -->
        th, td { border: 1px solid black; padding: 5px; text-align: center; }  <!-- 設(shè)置表格的單元格樣式 -->
        th { background-color: #f2f2f2; }  <!-- 設(shè)置表頭背景色 -->
        .subtotal { font-weight: bold; }  <!-- 小計(jì)行加粗 -->
        .total { font-weight: bold; font-size: 1.1em; }  <!-- 總計(jì)行加粗并設(shè)置字體大小 -->
    </style>
</head>
<body>
<h1 style="text-align: center;">${company}送貨清單</h1>  <!-- 頂部公司名稱 -->
 
<table>
    <tr>
        <th>序號(hào)</th>  <!-- 表頭:序號(hào) -->
        <th>供貨單號(hào)</th>  <!-- 表頭:供貨單號(hào) -->
        <th>產(chǎn)品名稱</th>  <!-- 表頭:產(chǎn)品名稱 -->
        <th>規(guī)格型號(hào)</th>  <!-- 表頭:規(guī)格型號(hào) -->
        <th>數(shù)量</th>  <!-- 表頭:數(shù)量 -->
        <th>單位</th>  <!-- 表頭:?jiǎn)挝?-->
        <th>備注</th>  <!-- 表頭:備注 -->
    </tr>
    <#assign totalQuantity = 0>  <!-- 總數(shù)量初始化 -->
    <#assign totalItems = 0>  <!-- 總項(xiàng)數(shù)初始化 -->
    <#assign sortedList = qdList?sort_by("model")>  <!-- 按照規(guī)格型號(hào)排序 -->
    <#assign currentModel = "">  <!-- 當(dāng)前型號(hào)初始化 -->
    <#assign subtotalQuantity = 0>  <!-- 小計(jì)數(shù)量初始化 -->
    <#assign subtotalItems = 0>  <!-- 小計(jì)項(xiàng)數(shù)初始化 -->
    <#list sortedList as item>  <!-- 遍歷排序后的列表 -->
        <#if item.model != currentModel>  <!-- 如果規(guī)格型號(hào)變了 -->
            <#if currentModel != "">  <!-- 如果當(dāng)前規(guī)格型號(hào)不是空 -->
                <tr class="subtotal">
                    <td colspan="4">小計(jì):${subtotalQuantity}${sortedList[0].unit} ${subtotalItems}軸</td>
                    <td>${subtotalQuantity}</td>
                    <td>${sortedList[0].unit}</td>
                    <td></td>
                </tr>
            </#if>
            <#assign currentModel = item.model>  <!-- 更新當(dāng)前型號(hào) -->
            <#assign subtotalQuantity = 0>  <!-- 重置小計(jì)數(shù)量 -->
            <#assign subtotalItems = 0>  <!-- 重置小計(jì)項(xiàng)數(shù) -->
        </#if>
        <tr>
            <td>${item?counter}</td>  <!-- 序號(hào) -->
            <td>${item.danhao}</td>  <!-- 單號(hào) -->
            <td>${item.name}</td>  <!-- 產(chǎn)品名稱 -->
            <td>${item.model}</td>  <!-- 規(guī)格型號(hào) -->
            <td>${item.num}</td>  <!-- 數(shù)量 -->
            <td>${item.unit}</td>  <!-- 單位 -->
            <td>${item.remark}</td>  <!-- 備注 -->
        </tr>
        <#assign itemNum = item.num?replace(",", "")?number>  <!-- 將數(shù)量轉(zhuǎn)為數(shù)字并處理逗號(hào) -->
        <#assign subtotalQuantity = subtotalQuantity + itemNum>  <!-- 累加小計(jì)數(shù)量 -->
        <#assign subtotalItems = subtotalItems + 1>  <!-- 累加小計(jì)項(xiàng)數(shù) -->
        <#assign totalQuantity = totalQuantity + itemNum>  <!-- 累加總數(shù)量 -->
        <#assign totalItems = totalItems + 1>  <!-- 累加總項(xiàng)數(shù) -->
    </#list>
    <#if currentModel != "">  <!-- 如果當(dāng)前規(guī)格型號(hào)不是空 -->
        <tr class="subtotal">
            <td colspan="4">小計(jì):${subtotalQuantity}${sortedList[0].unit} ${subtotalItems}軸</td>
            <td>${subtotalQuantity}</td>
            <td>${sortedList[0].unit}</td>
            <td></td>
        </tr>
    </#if>
    <tr class="total">
        <td colspan="4">合計(jì):${totalQuantity}${qdList[0].unit} ${totalItems}軸</td>
        <td>${totalQuantity}</td>
        <td>${qdList[0].unit}</td>
        <td></td>
    </tr>
</table>
 
<p>發(fā)貨聯(lián)系人:${contacts}</p>  <!-- 發(fā)貨聯(lián)系人 -->
<p>聯(lián)系電話:${contactsPhone}</p>  <!-- 聯(lián)系電話 -->
<p>日期:${date}</p>  <!-- 日期 -->
 
<p style="text-align: right;">收貨人(簽字):_______________</p>  <!-- 收貨人簽字 -->
<p style="text-align: right;">聯(lián)系電話:_______________</p>  <!-- 收貨人聯(lián)系電話 -->
<p style="text-align: right;">${customer}</p>  <!-- 客戶 -->
</body>
</html>

4.POM依賴

<!-- freemarker依賴,用于模板引擎,方便進(jìn)行頁(yè)面的渲染和數(shù)據(jù)的展示等操作 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<!-- Apache POI 的核心依賴,用于操作 Microsoft Office 格式的文檔,如 Excel、Word 等文件 -->
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi</artifactId>
    <version>5.0.0</version>
</dependency>
<!-- Apache POI 的 OOXML 擴(kuò)展依賴,主要用于處理 Office 2007 及以后版本的 OOXML 格式的文件,例如.xlsx 等 -->
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-ooxml</artifactId>
    <version>5.0.0</version>
</dependency>
<!-- OOXML 模式相關(guān)的依賴,提供了對(duì) OOXML 文檔結(jié)構(gòu)和內(nèi)容模式的支持,有助于 Apache POI 更好地操作 OOXML 格式文件 -->
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>ooxml-schemas</artifactId>
    <version>1.4</version>
</dependency>

WordController類深度解析

WordController類是整個(gè)應(yīng)用的核心控制器,負(fù)責(zé)協(xié)調(diào)數(shù)據(jù)生成和文檔創(chuàng)建的過(guò)程。讓我們逐步分析它的主要組成部分:

1.類結(jié)構(gòu)

public class WordController {
    // 方法定義...
}

這個(gè)類沒(méi)有繼承任何其他類,也沒(méi)有實(shí)現(xiàn)任何接口,是一個(gè)獨(dú)立的控制器類。

2.main方法

public static void main(String[] args) throws IOException {
    String filePath = "F:\\Poi2Word\\src\\main\\resources\\output";
    new WordController().generateWordFile(filePath);
}

這是應(yīng)用的入口點(diǎn)。

它設(shè)置了輸出文件的路徑,然后調(diào)用generateWordFile方法。

請(qǐng)注意:在常規(guī)的 Spring Boot 實(shí)際應(yīng)用場(chǎng)景下,我們一般不會(huì)直接在控制器類中使用 main 方法。此處之所以將 main 方法置于控制器中,純粹是出于演示目的,旨在讓相關(guān)流程更加直觀易懂。

而當(dāng)進(jìn)入到正式開(kāi)發(fā)環(huán)節(jié)時(shí),有幾個(gè)關(guān)鍵要點(diǎn)務(wù)必落實(shí):

其一,需要引入數(shù)據(jù)庫(kù)集成功能,將當(dāng)前所使用的測(cè)試數(shù)據(jù)全面替換為從數(shù)據(jù)庫(kù)中精準(zhǔn)查詢獲取的真實(shí)數(shù)據(jù),以此確保數(shù)據(jù)的準(zhǔn)確性與時(shí)效性;

其二,要對(duì)控制器進(jìn)行優(yōu)化改造,摒棄現(xiàn)有的演示模式,將其轉(zhuǎn)換為遵循標(biāo)準(zhǔn)規(guī)范的請(qǐng)求接口實(shí)現(xiàn)方式,進(jìn)而滿足實(shí)際業(yè)務(wù)需求,提升系統(tǒng)的穩(wěn)定性與可擴(kuò)展性。

3.generateWordFile方法

此方法的只要目的是生成Word文件,首先需要先收集和存儲(chǔ)測(cè)試數(shù)據(jù),存儲(chǔ)表格數(shù)據(jù)是將一條數(shù)據(jù)存儲(chǔ)在Map集合中,再將每一條數(shù)據(jù)存儲(chǔ)到List集合中。將其他數(shù)據(jù)存儲(chǔ)到單獨(dú)的一個(gè)Map集合中。然后確保輸出目錄存在,最后調(diào)用WordUtil中的exportMillCertificateWord方法生成文件,并輸出文件的生成位置。

// 生成 Word 文件的方法
public void generateWordFile(String directory) throws IOException {
    // 存儲(chǔ)測(cè)試數(shù)據(jù)的列表,每個(gè)元素都是一個(gè) Map,存儲(chǔ)了具體的信息
    List<Map<String, Object>> listMap = new ArrayList<>();
 
    // 添加測(cè)試數(shù)據(jù),調(diào)用 addTestData 方法添加一條記錄
    addTestData(listMap, "4600025747", "絕緣導(dǎo)線", "AC10kV,JKLYJ,300", 1500, "米", "盤(pán)號(hào):A1");
    //... 可以繼續(xù)調(diào)用 addTestData 方法添加更多測(cè)試數(shù)據(jù)...
 
    // 存儲(chǔ)最終要填充到 Word 模板的數(shù)據(jù)的 Map,包含各種信息
    HashMap<String, Object> map = new HashMap<>();
    // 將測(cè)試數(shù)據(jù)列表添加到 map 中,鍵為 "qdList"
    map.put("qdList", listMap);
    // 聯(lián)系人信息
    map.put("contacts", "張三");
    // 聯(lián)系人電話
    map.put("contactsPhone", "13988887777");
    // 日期信息
    map.put("date", "2025年01月18日");
    // 公司名稱
    map.put("company", "新電纜科技有限公司");
    // 客戶名稱
    map.put("customer", "國(guó)網(wǎng)北京市電力公司");
 
    // Word 模板文件的名稱
    String wordName = "template.ftl";
    // 生成的 Word 文件的名稱,使用當(dāng)前時(shí)間戳保證文件名的唯一性
    String fileName = "供貨清單" + System.currentTimeMillis() + ".doc";
    // 名稱信息,具體含義可能根據(jù)實(shí)際情況而定
    String name = "name";
 
    // 創(chuàng)建一個(gè)文件對(duì)象,用于表示輸出目錄
    File directoryFile = new File(directory);
    // 檢查輸出目錄是否存在,如果不存在則創(chuàng)建目錄
    if (!directoryFile.exists()) {
        directoryFile.mkdirs();
    }
 
    // 調(diào)用 WordUtil 的 exportMillCertificateWord 方法生成 Word 文件
    // 傳入目錄、數(shù)據(jù) Map、模板名稱、生成的文件名稱和名稱信息
    WordUtil.exportMillCertificateWord(directory, map, wordName, fileName, name);
 
    // 打印生成文件的成功信息
    System.out.println("文件成功生成在:" + directory + fileName);
}

這個(gè)方法完成以下任務(wù):

  • 創(chuàng)建一個(gè)一個(gè)List<Map<String,Object>>集合來(lái)存儲(chǔ)供貨清單數(shù)據(jù)
  • 使用addTestData方法添加多條測(cè)試數(shù)據(jù)
  • 創(chuàng)建一個(gè)Map集合來(lái)存儲(chǔ)企業(yè)名稱,發(fā)貨聯(lián)系人,聯(lián)系電話等信息
  • 確保輸出目錄存在
  • 調(diào)用WordUtil.exportMillCertificateWord方法來(lái)生成Word文檔

4.addTestData方法

這個(gè)方法用于創(chuàng)建單個(gè)供貨項(xiàng)目的數(shù)據(jù)

// 添加一條測(cè)試數(shù)據(jù)到 listMap 中
private void addTestData(List<Map<String, Object>> listMap, String danhao, String name, String model, int num, String unit, String remark) {
    // 創(chuàng)建一個(gè)新的 HashMap,用于存儲(chǔ)每一條數(shù)據(jù)
    Map<String, Object> item = new HashMap<>();
    
    // 將數(shù)據(jù)項(xiàng)依次放入 HashMap 中,"serNo" 表示序號(hào),使用 listMap 的大小+1 生成序號(hào)
    item.put("serNo", listMap.size() + 1);  // 序號(hào)是當(dāng)前列表的大小 + 1
    item.put("danhao", danhao);  // 供貨單號(hào)
    item.put("name", name);  // 產(chǎn)品名稱
    item.put("model", model);  // 規(guī)格型號(hào)
    item.put("num", String.valueOf(num));  // 數(shù)量,將整數(shù)轉(zhuǎn)為字符串
    item.put("unit", unit);  // 單位
    item.put("remark", remark);  // 備注
    
    // 將該條數(shù)據(jù)項(xiàng)添加到 listMap 列表中
    listMap.add(item);
}

這個(gè)方法完成以下任務(wù):

  • 它接收多個(gè)參數(shù),代表一個(gè)供貨項(xiàng)目的各個(gè)屬性。
  • 創(chuàng)建一個(gè)新的Map來(lái)存儲(chǔ)這個(gè)項(xiàng)目的數(shù)據(jù)。
  • 自動(dòng)計(jì)算序號(hào)(serNo)基于當(dāng)前列表的大小。
  • 將所有數(shù)據(jù)添加到Map中。
  • 將這個(gè)Map添加到供貨清單列表中。

WordUtil類深度解析

WordUtil類是整個(gè)文檔生成過(guò)程的核心,它封裝了FreeMarker模板引擎的配置和使用邏輯。讓我們逐步分析它的主要組成部分:

1.類結(jié)構(gòu)和靜態(tài)成員

public class WordUtil {
    private static Configuration configuration = null;
    private static final String templateFolder = WordUtil.class.getResource("/templates").getPath();
    
    // 其他方法...
}

configuration:這是FreeMarker的核心配置對(duì)象,用于設(shè)置模版加載路徑。

templateFolder:定義了模版文件的存儲(chǔ)路徑。使用getResource()方法確保在不同環(huán)境下都能正確找到模版文件。

2.靜態(tài)初始化塊

這段代碼的作用是初始化FreeMarker的Configuration對(duì)象,設(shè)置模版加載目錄以及編碼格式,以便FreeMarker后續(xù)能夠正確加載和處理模版文件。

// 靜態(tài)初始化塊,用于初始化 FreeMarker 配置
static {
    // 創(chuàng)建一個(gè) FreeMarker 配置對(duì)象,用于后續(xù)模板處理
    configuration = new Configuration();
    
    // 設(shè)置 FreeMarker 配置對(duì)象的默認(rèn)編碼為 "utf-8"
    configuration.setDefaultEncoding("utf-8");
    
    try {
        // 輸出模板文件夾路徑,幫助調(diào)試
        System.out.println(templateFolder);
        
        // 設(shè)置模板加載目錄為 templateFolder 指定的路徑,模板文件會(huì)從該目錄加載
        configuration.setDirectoryForTemplateLoading(new File(templateFolder));
    } catch (IOException e) {
        // 如果加載模板目錄時(shí)出現(xiàn)異常,打印錯(cuò)誤堆棧信息
        e.printStackTrace();
    }
}

這個(gè)靜態(tài)初始化塊在類加載時(shí)執(zhí)行,主要完成以下任務(wù):

  • 創(chuàng)建FreeMarker的Configuration對(duì)象
  • 設(shè)置默認(rèn)編碼為UTF-8,確保正確處理中文等字符
  • 設(shè)置模版加載目錄,這樣FreeMarker就知道從哪里查找加載模版文件了
  • 錯(cuò)誤處理:如果執(zhí)行過(guò)程中出現(xiàn)了IO異常,就會(huì)打印堆棧跟蹤

3.私有構(gòu)造函數(shù)

這個(gè)構(gòu)造函數(shù)防止類被實(shí)例化,確保WordUtil只能通過(guò)其靜態(tài)方法使用。

private WordUtil() {
    throw new AssertionError();
}

私有構(gòu)造函數(shù)的好處包括:

防止類被實(shí)例化

當(dāng)類的構(gòu)造函數(shù)被聲明為private時(shí),外部代碼無(wú)法直接創(chuàng)建該類的實(shí)例。這就意味著該類只能公國(guó)靜態(tài)方法訪問(wèn),確保類的功能是全局共享的。

實(shí)現(xiàn)單例模式的基礎(chǔ)

在一些設(shè)計(jì)模式中,例如單例模式,類只允許有一個(gè)實(shí)例,私有構(gòu)造函數(shù)確保了這一點(diǎn)。通過(guò)private構(gòu)造函數(shù),我們可以控制類的實(shí)例化過(guò)程,并確保只有一個(gè)實(shí)例被創(chuàng)建。

封裝類的內(nèi)部實(shí)現(xiàn)

私有構(gòu)造函數(shù)可以幫助隱藏類的具體實(shí)現(xiàn)細(xì)節(jié),外部代碼不需要關(guān)心如何創(chuàng)建類的實(shí)例,只需要使用類提供的靜態(tài)方法即可。這增加了類的封裝性,降低了與外部代碼的耦合度。

避免多余的對(duì)象創(chuàng)建

由于無(wú)法實(shí)例化類,每次調(diào)用靜態(tài)方法時(shí),都會(huì)使用已有的類實(shí)例,這可以避免無(wú)意義的對(duì)象創(chuàng)建,節(jié)省內(nèi)存和資源。

4.exportMillCertificateWord方法

這個(gè)方法的主要功能是通過(guò)加載指定的 FreeMarker 模板生成一個(gè)臨時(shí)的 Word 文檔,確保輸出目錄存在后,將臨時(shí)文件重命名并保存到指定的位置,同時(shí)在過(guò)程結(jié)束后清理臨時(shí)文件,并打印文件生成的成功消息。

// 導(dǎo)出 Word 文檔的方法
public static void exportMillCertificateWord(String outputDirectory, Map map, String wordName, String fileName, String name) throws IOException {
    // 獲取 FreeMarker 模板文件
    Template freemarkerTemplate = configuration.getTemplate(wordName);
    // 初始化一個(gè) File 對(duì)象,用于存儲(chǔ)生成的臨時(shí)文件
    File file = null;
    
    try {
        // 使用模板和數(shù)據(jù)創(chuàng)建 Word 文檔,返回臨時(shí)文件
        file = createDoc(map, freemarkerTemplate, name);
        
        // 創(chuàng)建目標(biāo)目錄的 File 對(duì)象
        File dir = new File(outputDirectory);
        
        // 如果目錄不存在,則創(chuàng)建該目錄
        if (!dir.exists()) {
            dir.mkdirs();  // 創(chuàng)建目錄及其父目錄
        }
        
        // 定義最終輸出文件的完整路徑(包括目錄和文件名)
        File outputFile = new File(outputDirectory, fileName);
        
        // 將臨時(shí)生成的文件重命名為目標(biāo)文件,并將其移動(dòng)到指定目錄
        file.renameTo(outputFile);
        
        // 打印輸出文件的絕對(duì)路徑,a通知文件生成成功
        System.out.println("文件成功生成在: " + outputFile.getAbsolutePath());
    } finally {
        // 最后,無(wú)論是否成功生成文件,都確保臨時(shí)文件被刪除
        if (file != null && file.exists()) {
            file.delete();  // 刪除臨時(shí)文件
        }
    }
}

這個(gè)方法是文檔導(dǎo)出的主要入口,主要實(shí)現(xiàn)了以下功能:

  • 加載指定的FreeMarker模版
  • 調(diào)用createDoc方法生成臨時(shí)文檔文件
  • 確保輸出目錄存在
  • 將臨時(shí)文件重命名并移動(dòng)到指定的輸出位置
  • 使用finally塊確保臨時(shí)文件被刪除,無(wú)論過(guò)程是否成功

5.createDoc方法

這個(gè)方法是創(chuàng)建文檔的核心方法,主要是通過(guò)創(chuàng)建一個(gè)臨時(shí)文件,使用指定的FreeMarker模版和數(shù)據(jù)模型將內(nèi)容填充到文件中,并確保文件使用UTF-8編碼進(jìn)行寫(xiě)入。該方法在執(zhí)行過(guò)程中捕獲異常并打印堆棧信息,確保發(fā)生錯(cuò)誤時(shí)能夠正確處理。最后。方法返回生成的文件對(duì)象,以便后續(xù)操作或保存。

// 創(chuàng)建文檔的方法,使用 FreeMarker 模板生成內(nèi)容并寫(xiě)入文件
private static File createDoc(Map<?, ?> dataMap, Template template, String name) {
    // 創(chuàng)建一個(gè)新的 File 對(duì)象,表示生成的文檔文件,文件名由參數(shù) "name" 提供
    File f = new File(name);
    
    try {
        // 使用 OutputStreamWriter 創(chuàng)建一個(gè)寫(xiě)入文件的 Writer 對(duì)象,設(shè)置編碼為 "utf-8"
        Writer w = new OutputStreamWriter(new FileOutputStream(f), "utf-8");
        
        // 使用 FreeMarker 模板將數(shù)據(jù)填充到文件中
        template.process(dataMap, w);
        
        // 關(guān)閉 Writer,確保所有內(nèi)容寫(xiě)入文件
        w.close();
    } catch (Exception ex) {
        // 捕獲異常并打印錯(cuò)誤堆棧信息
        ex.printStackTrace();
        
        // 拋出 RuntimeException,確保錯(cuò)誤被傳播到調(diào)用者
        throw new RuntimeException(ex);
    }
    
    // 返回生成的文件對(duì)象
    return f;
}

這個(gè)方法是實(shí)際創(chuàng)建文檔的核心,主要實(shí)現(xiàn)以下功能:

創(chuàng)建一個(gè)臨時(shí)文件。

使用OutputStreamWriter設(shè)置UTF-8編碼,確保正確處理所有字符。

調(diào)用FreeMarker的template.process()方法,將數(shù)據(jù)模型(dataMap)應(yīng)用到模板上。

關(guān)閉寫(xiě)入器。

如果過(guò)程中發(fā)生異常,打印堆棧跟蹤并拋出RuntimeException。

返回生成的文件對(duì)象。

6.WordUtil類總結(jié)

WordUtil 類通過(guò)封裝 FreeMarker 模板引擎的配置和文件操作,提供了一個(gè)簡(jiǎn)潔的文檔生成工具。它加載指定模板,使用數(shù)據(jù)模型填充內(nèi)容,創(chuàng)建臨時(shí)文件,并確保文件按照指定路徑保存。該類通過(guò)靜態(tài)方法確保全局共享功能,使用 UTF-8 編碼處理字符,捕獲異常并清理臨時(shí)文件,確保文檔生成過(guò)程的穩(wěn)定性和高效性。

FreeMarker模板深度解析

FreeMarker模板是整個(gè)文檔生成過(guò)程的核心,它定義了最終Word文檔的結(jié)構(gòu)和樣式。讓我們來(lái)逐步分析模板的主要組成部分

1.文檔結(jié)構(gòu)和樣式

<!DOCTYPE html> <!-- 聲明文檔類型為 HTML5 -->
<html>
<head>
    <!-- 設(shè)置文檔字符編碼為 UTF-8,支持中文和其他字符集 -->
    <meta charset="UTF-8">
    <!-- 設(shè)置頁(yè)面標(biāo)題,動(dòng)態(tài)插入公司名稱 -->
    <title>${company}送貨清單</title>
    <style>
        /* 設(shè)置頁(yè)面正文的字體為 SimSun(宋體),如果沒(méi)有則使用 serif */
        body { font-family: SimSun, serif; }
 
        /* 設(shè)置表格樣式:表格邊框合并,寬度100% */
        table { border-collapse: collapse; width: 100%; }
 
        /* 設(shè)置表格頭部和單元格的邊框、內(nèi)邊距和文本居中對(duì)齊 */
        th, td { border: 1px solid black; padding: 5px; text-align: center; }
 
        /* 設(shè)置表頭背景色為淺灰色 */
        th { background-color: #f2f2f2; }
 
        /* 設(shè)置小計(jì)行字體加粗 */
        .subtotal { font-weight: bold; }
 
        /* 設(shè)置合計(jì)行字體加粗,字體大小稍大 */
        .total { font-weight: bold; font-size: 1.1em; }
    </style>
</head>
<body>
    <!-- 頁(yè)面標(biāo)題,居中顯示公司名稱和送貨清單 -->
    <h1 style="text-align: center;">${company}送貨清單</h1>
    <!-- 表格內(nèi)容將在這里生成,動(dòng)態(tài)插入數(shù)據(jù) -->
</body>
</html>

這段代碼通過(guò)HTML和內(nèi)嵌CSS定義了頁(yè)面布局和樣式:

動(dòng)態(tài)公司名稱:<title>標(biāo)簽使用${company}插入動(dòng)態(tài)的公司名稱,顯示在瀏覽器標(biāo)簽中。

字體和表格樣式:

  • 設(shè)置頁(yè)面字體為宋體(Simsun)
  • 定義表格邊框合并、100%寬度,并使單元格內(nèi)容居中

小計(jì)和總計(jì)行樣式:為小計(jì)行加粗字體,并為總計(jì)行加粗且增大字體,突出顯示重要數(shù)據(jù)。

2.表格結(jié)構(gòu)和動(dòng)態(tài)數(shù)據(jù)插入

<table>
    <!-- 表頭,定義表格的列名 -->
    <tr>
        <th>序號(hào)</th>  <!-- 序號(hào) -->
        <th>供貨單號(hào)</th>  <!-- 供貨單號(hào) -->
        <th>產(chǎn)品名稱</th>  <!-- 產(chǎn)品名稱 -->
        <th>規(guī)格型號(hào)</th>  <!-- 規(guī)格型號(hào) -->
        <th>數(shù)量</th>  <!-- 數(shù)量 -->
        <th>單位</th>  <!-- 單位 -->
        <th>備注</th>  <!-- 備注 -->
    </tr>
 
    <!-- 初始化總計(jì)和小計(jì)相關(guān)變量 -->
    <#assign totalQuantity = 0>  <!-- 總數(shù)量 -->
    <#assign totalItems = 0>  <!-- 總項(xiàng)數(shù) -->
    <#assign sortedList = qdList?sort_by("model")>  <!-- 按照規(guī)格型號(hào)對(duì)數(shù)據(jù)進(jìn)行排序 -->
    <#assign currentModel = "">  <!-- 當(dāng)前規(guī)格型號(hào) -->
    <#assign subtotalQuantity = 0>  <!-- 小計(jì)數(shù)量 -->
    <#assign subtotalItems = 0>  <!-- 小計(jì)項(xiàng)數(shù) -->
 
    <!-- 遍歷排序后的列表 -->
    <#list sortedList as item>
        <!-- 如果當(dāng)前項(xiàng)的規(guī)格型號(hào)與上一項(xiàng)不同,則輸出上一項(xiàng)的小計(jì) -->
        <#if item.model != currentModel>
            <#if currentModel != "">
                <!-- 輸出上一規(guī)格型號(hào)的小計(jì)行 -->
                <tr class="subtotal">
                    <td colspan="4">小計(jì):${subtotalQuantity}${sortedList[0].unit} ${subtotalItems}軸</td>
                    <td>${subtotalQuantity}</td>
                    <td>${sortedList[0].unit}</td>
                    <td></td>
                </tr>
            </#if>
            <!-- 更新當(dāng)前規(guī)格型號(hào)為當(dāng)前項(xiàng)的規(guī)格型號(hào),并重置小計(jì) -->
            <#assign currentModel = item.model>
            <#assign subtotalQuantity = 0>
            <#assign subtotalItems = 0>
        </#if>
 
        <!-- 輸出當(dāng)前行數(shù)據(jù) -->
        <tr>
            <td>${item?counter}</td>  <!-- 序號(hào),使用 FreeMarker 的 counter 計(jì)數(shù) -->
            <td>${item.danhao}</td>  <!-- 供貨單號(hào) -->
            <td>${item.name}</td>  <!-- 產(chǎn)品名稱 -->
            <td>${item.model}</td>  <!-- 規(guī)格型號(hào) -->
            <td>${item.num}</td>  <!-- 數(shù)量 -->
            <td>${item.unit}</td>  <!-- 單位 -->
            <td>${item.remark}</td>  <!-- 備注 -->
        </tr>
 
        <!-- 更新小計(jì)和總計(jì)的數(shù)量和項(xiàng)數(shù) -->
        <#assign itemNum = item.num?replace(",", "")?number>  <!-- 將數(shù)量轉(zhuǎn)為數(shù)字并處理逗號(hào) -->
        <#assign subtotalQuantity = subtotalQuantity + itemNum>  <!-- 累加小計(jì)數(shù)量 -->
        <#assign subtotalItems = subtotalItems + 1>  <!-- 累加小計(jì)項(xiàng)數(shù) -->
        <#assign totalQuantity = totalQuantity + itemNum>  <!-- 累加總數(shù)量 -->
        <#assign totalItems = totalItems + 1>  <!-- 累加總項(xiàng)數(shù) -->
    </#list>
 
    <!-- 如果最后一項(xiàng)有數(shù)據(jù),輸出最后的規(guī)格型號(hào)小計(jì) -->
    <#if currentModel != "">
        <tr class="subtotal">
            <td colspan="4">小計(jì):${subtotalQuantity}${sortedList[0].unit} ${subtotalItems}軸</td>
            <td>${subtotalQuantity}</td>
            <td>${sortedList[0].unit}</td>
            <td></td>
        </tr>
    </#if>
 
    <!-- 輸出最終的合計(jì)行 -->
    <tr class="total">
        <td colspan="4">合計(jì):${totalQuantity}${qdList[0].unit} ${totalItems}軸</td>  <!-- 顯示合計(jì)的數(shù)量和項(xiàng)數(shù) -->
        <td>${totalQuantity}</td>  <!-- 合計(jì)數(shù)量 -->
        <td>${qdList[0].unit}</td>  <!-- 單位 -->
        <td></td>
    </tr>
</table>

表格結(jié)構(gòu):使用 <table> 標(biāo)簽創(chuàng)建表格,并通過(guò) <th> 定義表頭,包含7列:序號(hào)、供貨單號(hào)、產(chǎn)品名稱等。

動(dòng)態(tài)數(shù)據(jù)插入:使用 FreeMarker <#list> 遍歷排序后的清單數(shù)據(jù),并通過(guò) ${item.屬性名} 動(dòng)態(tài)插入每項(xiàng)數(shù)據(jù),如 ${item.danhao} 插入供貨單號(hào)。

小計(jì)和總計(jì)計(jì)算:

  • 通過(guò) <#assign> 定義變量如 totalQuantity 和 subtotalQuantity,在循環(huán)中累加數(shù)量。
  • 使用 <#if> 判斷條件,插入小計(jì)行,并在循環(huán)結(jié)束后插入總計(jì)行。

數(shù)據(jù)處理:

  • 使用 sortedList = qdList?sort_by("model") 按型號(hào)對(duì)清單數(shù)據(jù)進(jìn)行排序。
  • 處理數(shù)量 itemNum = item.num?replace(",", "")?number,移除逗號(hào)并轉(zhuǎn)換為數(shù)字,確保計(jì)算正確。

格式化輸出:

  • 小計(jì)和總計(jì)行使用 colspan 屬性合并單元格,確保表格顯示整潔。
  • 使用 CSS 類 subtotal 和 total 為小計(jì)和總計(jì)行應(yīng)用加粗和突出顯示的樣式。

總結(jié):此表格通過(guò) FreeMarker 動(dòng)態(tài)插入數(shù)據(jù)、計(jì)算小計(jì)和總計(jì),并通過(guò)合適的排序和格式化樣式,確保清單展示清晰且易于閱讀。

最后,模板還包括了一些額外信息:

<p>發(fā)貨聯(lián)系人:${contacts}</p>
<p>聯(lián)系電話:${contactsPhone}</p>
<p>日期:${date}</p>
 
<p style="text-align: right;">收貨人(簽字):_______________</p>
<p style="text-align: right;">聯(lián)系電話:_______________</p>
<p style="text-align: right;">${customer}</p>

這部分添加了額外的聯(lián)系信息和簽名區(qū)域,進(jìn)一步完善了文檔的實(shí)用性。

總的來(lái),這個(gè)FreeMarker模板展示了如何結(jié)合HTML、CSS和FreeMarker的模板語(yǔ)法來(lái)創(chuàng)建一個(gè)復(fù)雜、動(dòng)態(tài)且格式良好的文檔。它不僅能夠準(zhǔn)確地呈現(xiàn)數(shù)據(jù),還能執(zhí)行必要的計(jì)算和格式化,從而生成一個(gè)專業(yè)的供貨清單文檔。

總結(jié)

通過(guò)使用SpingBoot、Apache POI和FreeMarker,我們成功自動(dòng)化了電纜供貨清單的生成過(guò)程。這不僅提高了效率,還減少了人為錯(cuò)誤。本解決方案的模塊化設(shè)計(jì)使其易于維護(hù)和擴(kuò)展。

以上就是Java使用FreeMarker實(shí)現(xiàn)動(dòng)態(tài)生成Word文檔的詳細(xì)內(nèi)容,更多關(guān)于Java FreeMarker生成Word的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • SpringMVC?RESTFul實(shí)戰(zhàn)案例訪問(wèn)首頁(yè)

    SpringMVC?RESTFul實(shí)戰(zhàn)案例訪問(wèn)首頁(yè)

    這篇文章主要為大家介紹了SpringMVC?RESTFul實(shí)戰(zhàn)案例訪問(wèn)首頁(yè),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-05-05
  • Java中Array、List、ArrayList的區(qū)別及說(shuō)明

    Java中Array、List、ArrayList的區(qū)別及說(shuō)明

    這篇文章主要介紹了Java中Array、List、ArrayList的區(qū)別及說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-07-07
  • 詳談Java中的二進(jìn)制及基本的位運(yùn)算

    詳談Java中的二進(jìn)制及基本的位運(yùn)算

    下面小編就為大家?guī)?lái)一篇詳談Java中的二進(jìn)制及基本的位運(yùn)算。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-07-07
  • Java中Object類的理解和使用

    Java中Object類的理解和使用

    Object類是java.lang包下的核心類,Object類是所有類的父類,何一個(gè)類時(shí)候如果沒(méi)有明確的繼承一個(gè)父類的話,那么它就是Object的子類,本文將通過(guò)代碼示例詳細(xì)介紹一下Java中Object類的理解和使用,需要的朋友可以參考下
    2023-06-06
  • 解決Spring導(dǎo)出可以運(yùn)行的jar包問(wèn)題

    解決Spring導(dǎo)出可以運(yùn)行的jar包問(wèn)題

    最近需要解決Maven項(xiàng)目導(dǎo)入可執(zhí)行的jar包的問(wèn)題,如果項(xiàng)目不包含Spring,那么使用mvn assembly:assembly即可,這篇文章主要介紹了Spring導(dǎo)出可以運(yùn)行的jar包,需要的朋友可以參考下
    2023-03-03
  • MyBatis-plus實(shí)現(xiàn)逆向生成器

    MyBatis-plus實(shí)現(xiàn)逆向生成器

    本文主要介紹了MyBatis-plus實(shí)現(xiàn)逆向生成器,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2022-08-08
  • SpringBoot中dubbo+zookeeper實(shí)現(xiàn)分布式開(kāi)發(fā)的應(yīng)用詳解

    SpringBoot中dubbo+zookeeper實(shí)現(xiàn)分布式開(kāi)發(fā)的應(yīng)用詳解

    這篇文章主要介紹了SpringBoot中dubbo+zookeeper實(shí)現(xiàn)分布式開(kāi)發(fā)的應(yīng)用詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-11-11
  • Java中新建一個(gè)文件、目錄及路徑操作實(shí)例

    Java中新建一個(gè)文件、目錄及路徑操作實(shí)例

    這篇文章主要給大家介紹了關(guān)于Java中新建一個(gè)文件、目錄及路徑操作的相關(guān)資料,新建文件、目錄及路徑是我們?nèi)粘i_(kāi)發(fā)中經(jīng)常會(huì)遇到的一個(gè)需求,本文通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2023-12-12
  • Java之經(jīng)典排序算法

    Java之經(jīng)典排序算法

    這篇文章主要介紹了Java的一些經(jīng)典排序算法,對(duì)Java算法感興趣的小伙伴可以詳細(xì)參考閱讀本文,對(duì)同學(xué)們有一定的參考價(jià)值
    2023-03-03
  • maven基礎(chǔ)教程——簡(jiǎn)單了解maven的特點(diǎn)與功能

    maven基礎(chǔ)教程——簡(jiǎn)單了解maven的特點(diǎn)與功能

    這篇文章主要介紹了Maven基礎(chǔ)教程的相關(guān)資料,文中講解非常細(xì)致,幫助大家開(kāi)始學(xué)習(xí)maven,感興趣的朋友可以了解下
    2020-07-07

最新評(píng)論