整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          Java技術:SpringBoot集成FreeMarker生成word文件

          天給大家分享SpringBoot集成FreeMarker模板引擎生成word文件的用法,感興趣的可以學一下,完整源碼地址在文章末尾處,歡迎互相溝通交流!

          一、什么是FreeMarker?

          FreeMarker 是一款開源的模板引擎: 是一種基于模板和要動態填充的數據,可以用來動態渲染生成輸出文本(HTML網頁,Word文檔,電子郵件,配置文件,源代碼等)的通用技術。

          模板編寫為FreeMarker Template Language (FTL):它是簡單的,專用的語言, 不是像PHP那樣擁有完整成熟的編程語言。所以它主要專注于如何展現數據,具體要展示什么數據那就需要成熟的編程語言來實現(Java、C#、Python)等。

          FreeMarker原理圖如下:

          二、示例代碼

          1、導入freemarker依賴包

          <!--freemarker制作Html郵件模板依賴包-->
          <dependency>
              <groupId>org.springframework.boot</groupId>
              <artifactId>spring-boot-starter-freemarker</artifactId>
          </dependency>
          

          2、application.yml 配置freemarker

          freemarker:
            cache: false #是否啟用緩存,開發環境不建議啟動因為涉及經常修改模板調試
            settings:
              classic_compatible: true
            suffix: .html #一般格式tpl居多
            charset: UTF-8
            template-loader-path: classpath:/templates/ #模板路徑,一般都是這個
          

          3、新建IExportService.java 類

          package com.springboot.email.email.service;
          
          import javax.servlet.http.HttpServletResponse;
          import java.util.Map;
          
          public interface IExportService {
              /**
               * 導出word文件到指定目錄
               */
              void exportDocFile(String fileName, String tplName, Map<String, Object> data) throws Exception;
              /**
               * 導出word文件到客戶端
               */
              void exportDocToClient(HttpServletResponse response, String fileName, String tplName, Map<String, Object> data) throws Exception;
          }
          
          

          4、新建IExportService.java接口實現類ExportServiceImpl.java

          package com.springboot.email.email.service.impl;
          
          import com.springboot.email.email.service.IExportService;
          import freemarker.template.Template;
          import org.springframework.beans.factory.annotation.Autowired;
          import org.springframework.stereotype.Service;
          import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;
          
          import javax.servlet.http.HttpServletResponse;
          import java.io.*;
          import java.net.URLEncoder;
          import java.util.Map;
          
          @Service
          public class ExportServiceImpl implements IExportService {
              private String encoding;
              private String exportPath = "D:\\export\\";
              @Autowired
              private FreeMarkerConfigurer freeMarkerConfigurer;
              public Template getTemplate(String name) throws Exception {
                  return freeMarkerConfigurer.getConfiguration().getTemplate("test.html");
              }
          
          
              /**
               * 導出本地文件到指定的目錄
               */
              @Override
              public void exportDocFile(String fileName, String tplName, Map<String, Object> data) throws Exception {
                  //如果目錄不存在,則創建目錄
                  File exportDirs = new File(exportPath);
                  if (!exportDirs.exists()) {
                      exportDirs.mkdirs();
                  }
                  Writer writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(exportPath + fileName), encoding));
                  getTemplate(tplName).process(data, writer);
              }
          
              /**
               * 導出word文件到瀏覽器客戶端
               */
              @Override
              public void exportDocToClient(HttpServletResponse response, String fileName, String tplName, Map<String, Object> data) throws Exception {
                  response.reset();
                  response.setCharacterEncoding("UTF-8");
                  response.setContentType("application/msword");
                  response.setHeader("Content-Disposition", "attachment; filename=" +  URLEncoder.encode(fileName , "UTF-8"));
                  // 把本地文件發送給客戶端
                  Writer out = response.getWriter();
                  Template template = getTemplate(tplName);
                  template.process(data, out);
                  out.close();
              }
          }
          
          

          5、新建模板文件test.html

          具體文件參考源碼,模板文件的制作方式,新建word文件調整格式后→另存為xml格式的文件→局部調整文件循環標記→然后格式保存為html格式的文件 放在項目當中去。

          如果有不清楚的可以留言交流。

          6、新建測試類ExportController.java

          @RestController
          @RequestMapping("/export")
          public class ExportController {
              @Autowired
              private IExportService exportService;
              @RequestMapping(value = "/testWord", method= RequestMethod.GET)
              public void exportWord(HttpServletRequest request, HttpServletResponse response) throws Exception {
                  String fileName = "測試word導出.doc"; //文件名稱
                  // 設置頭部數據
                  Map<String,Object> dataMap = new HashMap<>();
                  dataMap.put("name","小明");
                  dataMap.put("regAddress","蘇州");
                  // 設置表格數據
                  List<ScoreVo> list=new ArrayList<>();
                  ScoreVo vo1=new ScoreVo();
                  vo1.setCourseName("英語");
                  vo1.setScore(95);
                  vo1.setRank(3);
                  ScoreVo vo2=new ScoreVo();
                  vo2.setCourseName("數學");
                  vo2.setScore(100);
                  vo2.setRank(1);
                  list.add(vo1);
                  list.add(vo2);
                  dataMap.put("courseList",list);
                  exportService.exportDocToClient(response, fileName, "test.html", dataMap);
              }
          }
          

          訪問地址:http://localhost:8080/export/testWord

          導出文件的效果:

          Gitee地址:https://gitee.com/hgm1989/springboot-email.git

          ender,顧名思義,要進行頁面渲染。Go 語言不但自帶有強大的 http 庫,還自帶了 HTML 模板引擎。Echo 框架對模板引擎進行了一些額外處理,并提供了給用戶自定義頁面渲染的接口。本文就相關問題進行探討。

          模板渲染

          Echo 框架的 Context 接口提供了下面的方法進行頁面渲染:

          // echo 包中 Context 接口的方法
          Render(code int, name string, data interface{}) error

          其中,code 是 HTTP Status,name 是定義的模板名,data 是模板可能需要的數據。執行這個方法后,通過數據渲染模板,并發送帶有 HTTP 狀態的 text/html 響應。可以通過 Echo.Renderer 來注冊模板,從而允許我們使用任何模板引擎。

          Renderer 接口定義如下:

          // Renderer is the interface that wraps the Render function.
          type Renderer interface {
            Render(io.Writer, string, interface{}, Context) error
          }

          這里可能會有點迷糊,怎么有兩個 Render 方法,而且它們的簽名還不一樣。這里的邏輯是這樣的:

          • echo.Echo 類型有一個 Renderer 接口類型的字段,用來注冊模板引擎;
          • echo.Context 接口類型有一個 Render 方法,在 Handle 中我們通過調用 Context 的 Render 方法進行模板渲染;
          • 在 Context 的 Render 方法內部(當然是 echo 中 Context 接口的默認實現),會調用 echo.Echo 的字段 Renderer 的 Render 方法,進行具體的模板渲染;

          這里是具體的渲染源碼:

          func (c *context) Render(code int, name string, data interface{}) (err error) {
           if c.echo.Renderer == nil {
            return ErrRendererNotRegistered
           }
           buf := new(bytes.Buffer)
           if err = c.echo.Renderer.Render(buf, name, data, c); err != nil {
            return
           }
           return c.HTMLBlob(code, buf.Bytes())
          }

          可見,如果調用了 Context#Render 進行模板渲染,但并沒有注冊模板引擎則會報錯(ErrRendererNotRegistered)。

          集成標準庫模板引擎

          1、我們先定義一個類型:Template,然后實現 Echo.Renderer 接口,即提供 Render 方法。

          type Template struct {
              templates *template.Template
          }
          
          func (t *Template) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
              return t.templates.ExecuteTemplate(w, name, data)
          }

          2、接著預編譯一個模板。定義一個模板文件:template/index.html,內容如下:

          {{define "index"}}Hello, {{.}}!{{end}}

          然后預編譯得到 Template 的實例:

          tpl := &Template{
              templates: template.Must(template.ParseGlob("template/*.html")),
          }

          3、注冊模板引擎:

          e := echo.New()
          
          e.Renderer = tpl

          4、在 Handler 中渲染模板:

          e.GET("/", func(ctx echo.Context) error {
            return ctx.Render(http.StatusOK, "index", "studygolang")
          })

          注意這里的 index 是模板文件中 define "index" ,而不是文件名。

          編譯后運行,瀏覽器正常顯示:Hello,studygolang!

          通用化定制

          一般的,頁面會有一些通用的部分,比如頭部、尾部等。所以業界通常的做法是有一個 layout,而且還可能不止一個 layout,因為普通用戶看到的和后臺看到的頭部、尾部一般會不一樣。那這樣的通用化定制需求該如何集成到 Echo 的 Render 中呢?

          先考慮只有一種 layout 的情況。定義一個類型 layoutTemplate,實現 Echo.Renderer 接口:

          type layoutTemplate struct{}
          
          var LayoutTemplate = &layoutTemplate{}
          
          func (l *layoutTemplate) Render(w io.Writer, contentTpl string, data interface{}, ctx echo.Context) error {
           layout := "layout.html"
           tpl, err := template.New(layout).ParseFiles("template/common/"+layout, "template/"+contentTpl)
           if err != nil {
            return err
           }
          
           return tpl.Execute(w, data)
          }

          然后注冊該 Renderer,并在 Handler 中渲染,注意 ctx.Render 的第二個參數,跟上面說的不一樣,我們傳遞的是子模板的文件名:index.html。

          e := echo.New()
          
          e.Renderer = render.LayoutTemplate
          
          e.GET("/", func(ctx echo.Context) error {
            return ctx.Render(http.StatusOK, "index.html", nil)
          })

          這里用到了兩個模板文件:layout.html 和 index.html,來源 Hugo 的 soho 這個模板。

          <!DOCTYPE html>
          <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
          <head>
            <meta http-equiv="content-type" content="text/html; charset=utf-8">
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
            <title>Echo博客系統</title>
          
            <meta name="author" content="Go語言中文網站長polaris">
          
            <meta name="keywords" content="" />
            <meta name="description" content="" />
          
            <link type="text/css" rel="stylesheet" href="/static/css/print.css" media="print">
            <link type="text/css" rel="stylesheet" href="/static/css/poole.css"> 
            <link type="text/css" rel="stylesheet" href="/static/css/hyde.css">
          
            <link rel="stylesheet"
                  href="https://fonts.googleapis.com/css?family=Open+Sans:400,400i,700&display=swap">
          
            <link rel="stylesheet"
                  href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.12.1/css/all.min.css"
                  integrity="sha256-mmgLkCYLUQbXn0B1SRqzHar6dCnv9oZFPEC1g1cwlkk="
                  crossorigin="anonymous" />
          
            <link rel="apple-touch-icon-precomposed"
                  sizes="144x144"
                  href="https://themes.gohugo.io//theme/soho/apple-touch-icon-144-precomposed.png">
          
            <link rel="shortcut icon" href="https://themes.gohugo.io//theme/soho/favicon.png">
          
            </head>
          
          <body>
            <aside class="sidebar">
              <div class="container">
                  <div class="sidebar-about">
                      <div class="author-image">
                          <img src="https://themes.gohugo.io/theme/soho/images/profile.png" class="img-circle img-headshot center" alt="Profile Picture">
                      </div>
                      <h1>Echo-Gopher</h1>
                  </div>
          
                  <nav>
                      <ul class="sidebar-nav">
                          <li> <a href="/">Home</a> </li>
                          <li> <a href="/about/"> About </a> </li>
                      </ul>
                  </nav>
          
                  <section class="social-icons">
          
                      <a href="https://github.com/polaris1119" rel="me" title="GitHub">
                          <i class="fab fa-github" aria-hidden="true"></i>
                      </a>
                      
                      <a href="https://weibo.com/studygolang" rel="me" title="Weibo">
                          <i class="fab fa-weibo" aria-hidden="true"></i>
                      </a>
                      
                  </section>
              </div>
            </aside>
          
            <main class="content container">
              {{template "content" .}}
            </main>
          
            <footer>
              <div class="copyright">
                ? polaris 2020 · <a href="https://creativecommons.org/licenses/by-sa/4.0">CC BY-SA 4.0</a>
              </div>
            </footer>
          
          <script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.12.1/js/all.min.js"
            integrity="sha256-MAgcygDRahs+F/Nk5Vz387whB4kSK9NXlDN3w58LLq0="
            crossorigin="anonymous"></script>
            
          </body>
          </html>

          這是 layout.html 的內容,核心在于 {{template "content" .}},表示具體內容模板需要定義 content,所以看看 index.html 文件:

          {{define "content"}}
          <div class="posts">
              <article class="post">
                  <h2 class="post-title">
                      <a href="/">Echo 系列教程 — 定制篇3:自定義 Logger,用你喜歡的日志庫</a>
                  </h2>
          
                  <div class="post-date">
                      <time datetime="2020-03-06T00:00:00Z">Mar 06, 2020</time> · 3 min read
                  </div>
                  在知識星球簡書項目中,我們分析對比了目前的一些日志庫。雖然 Go 標準庫有一個 log,但功能有限,所以才出現了很多第三方的日志庫。
                  <div class="read-more-link">
                      <a href="http://blog.studygolang.com/2020/03/echo-custom-logger/">閱讀全文</a>
                  </div>
              </article>
          
              <article class="post">
                  <h2 class="post-title">
                      <a href="/">Echo 系列教程 — 定制篇2:自定義 Validator,進行輸入校驗</a>
                  </h2>
          
                  <div class="post-date">
                      <time datetime="2020-02-28T00:00:00Z">Feb 28, 2020</time> · 4 min read
                  </div>
                  上一篇講 Binder 時提到,參數自動綁定和校驗是 Web 框架很重要的兩個功能,可以極大的提升開發速度,并更好的保證數據的可靠性(服務端數據校驗很重要)。
                  <div class="read-more-link">
                      <a href="http://blog.studygolang.com/2020/02/echo-custom-validator/">閱讀全文</a>
                  </div>
              </article>
          </div>
          {{end}}

          運行后打開瀏覽器訪問 http://localhost:2020 :

          接下來看看如何處理多個 layout 的情況。

          因為 Render 的簽名是固定的,不同的 layout 通過什么方式告知 Render 呢?觀察 Render 方法的參數:

          Render(w io.Writer, name string, data interface{}, ctx echo.Context)

          可以在 data 和 ctx 上下功夫:

          1. 將 data 指定為 map[string]interface{},layout 通過 data 傳遞;
          2. 通過 ctx 的 Set 方法設置 layout,方法內通過 ctx.Get 獲取 layout;

          先看第 1 種方式:

          // NoNavRender 沒有導航的 layout html 輸出
          func NoNavRender(ctx echo.Context, contentTpl string, data map[string]interface{}) error {
           if data == nil {
            data = make(map[string]interface{})
           }
           data["layout"] = "nonav_layout.html"
          
           return ctx.Render(http.StatusOK, contentTpl, data)
          }

          在 render 包中增加了一個 NoVaRender 函數,該函數要求 data 必須是 map[string]interface{},這樣就可以做到將 layout 傳遞給 Render 方法,不過因為 Render 方法的 data 參數是 interface{} 類型,因此得做類型斷言。

          layout := "layout.html"
          
          if data != nil {
            if dataMap, ok := data.(map[string]interface{}); ok {
              if layoutInter, ok := dataMap["layout"]; ok {
                layout = layoutInter.(string)
              }
            }
          }

          看看第 2 種方式如何實現:

          // NoNavRender 沒有導航的 layout html 輸出
          func NoNavRender(ctx echo.Context, contentTpl string, data interface{}) error {
           ctx.Set("layout", "nonav_layout.html")
          
           return ctx.Render(http.StatusOK, contentTpl, data)
          }

          在 Render 中獲取 layout 的值:

          layout := "layout.html"
          
          layoutInter := ctx.Get("layout")
          if layoutInter != nil {
            layout = layoutInter.(string)
          }

          兩種方式個人覺得第 2 種更優雅。不過需要注意的是,兩種方式要注意 layout 不能沖突,也就是不能他用。

          另外,我個人建議,data 參數永遠要么傳遞 nil,要么傳遞 map[string]interface{} 。個人感覺 Echo 的 Render 方法 data 參數的類型不應該用 interface{} 而是用 map[string]interface{},這樣可以更方便地往 data 中加入更多全局的數據。在簡書項目中,我們會通過其他方式彌補這個問題。

          小結

          通過本節,你應該掌握了 Render 的使用、集成和大項目 layout 的處理。

          額外提一句,因為 Context.Render 方法最終是調用的 Context.HTML 方法進行渲染,因此我們也完全可以拋棄 Render 方法,而是使用自己的 Render。目前簡書的代碼(后續會改掉)和 studygolang 的源碼采用的就是完全拋棄 Context.Render 的方式,主要考慮還是有一些 Render 不能很好滿足的地方,比如上面說的多 layout、data 類型等,不過也是可以解決的。因此還是建議采用 Echo 框架的 Render。

          本節完整代碼點這里: https://github.com/polaris1119/go-echo-example/tree/0cd46e8b1f38317439e95d55e3fe29a173a2e3c1。

          寫公共方法,這里以賦值到 laytpl 對象為例

          1. 之所以將方法暴露給寫成方法,是便于能讀取到。

          2. toDateString(d, format) 方法接受兩個參數。其中 d 可以是日期對象,也可以是毫秒數。format 是日期字符的格式,你可以隨意定義,如:yyyy年MM月dd日

          在列模板中調用時間戳的處理方法

          講解:

          d.time 中的 time 即是你接口返回的字段,如果是 unix 時間戳,這里記得要 d.time*1000,如果是毫秒數,這里直接傳 d.time 即可。


          主站蜘蛛池模板: 一区二区免费电影| 亚洲福利一区二区三区| 亚洲码欧美码一区二区三区| 精品女同一区二区| 精品国产鲁一鲁一区二区| 中文字幕一区二区人妻| 麻豆精品人妻一区二区三区蜜桃 | A国产一区二区免费入口| 亚洲AV无码一区二区三区鸳鸯影院 | 国产精品免费视频一区| 精品国产一区二区三区AV| 色窝窝无码一区二区三区| 熟女大屁股白浆一区二区| 亚洲国产高清在线一区二区三区| 另类ts人妖一区二区三区| 中文字幕AV一区二区三区| 亚洲香蕉久久一区二区| 99久久精品国产一区二区成人| 久久精品岛国av一区二区无码| 日韩精品在线一区二区| 国产日韩一区二区三免费高清 | 国产伦精品一区二区三区精品| 夜夜高潮夜夜爽夜夜爱爱一区| 无码中文人妻在线一区二区三区| 亚洲狠狠狠一区二区三区| 欧美亚洲精品一区二区| 国产成人精品亚洲一区| 一区二区免费国产在线观看| 上原亚衣一区二区在线观看| 国产在线一区二区三区| 99久久无码一区人妻a黑| 国产suv精品一区二区6| 大屁股熟女一区二区三区| 亚洲AV美女一区二区三区 | 国产一区内射最近更新| 国产凹凸在线一区二区| 亚洲精品国产suv一区88| 美女视频免费看一区二区| 国产内射999视频一区| 尤物精品视频一区二区三区| av无码一区二区三区|