Spring Boot中實現Thymeleaf通知

banq發表於2024-07-04

當應用程式執行潛在的關鍵操作(例如編輯、儲存或從資料庫中刪除資料)時,建議通知使用者操作的成功或失敗。這篇文章介紹瞭如何在 Thymeleaf 模板中顯示通知的基本解決方案。

假設讀者具備 Spring Boot、Thymeleaf 和 Bootstrap 的一般知識來應用這些內容。

這個想法是在應用程式中為通知分配一個顯示區域,並僅在應該顯示通知時啟用該區域,讓使用者可以選擇在閱讀後關閉通知。該區域可以建立為片段幷包含在所需的模板中,或者包含在與應用程式檢視關聯的所有模板中。

需要一個變數來充當顯示或不顯示這些通知的“鍵”。由於從控制器的每個路由管理器傳送訊息有些費力,因此我將使用 http 會話過濾器,在其中儲存訊息的值和要顯示的訊息型別。

最後,使用訊息處理程式,我將管理訊息的刪除,以便它停止顯示。

建立了一個名為的程式碼片段messageAlert.html,並將其包含在fragments資源模板的資料夾中。這是一個可以隨意修改的示例,以使其設計適應將要使用的應用程式。

<div th:fragment=<font>"messageAlert" th:if="${session.message != ''}" class="container">
    <form th:action=
"@{/message}" method="POST">

        <input type=
"hidden" th:name="returnUrl" th:value="${returnUrl}">

        <div class=
"alert alert-dismissible fade show" th:classappend="${session.messageType} == 'danger'? 'alert-danger' : 'alert-info'" role="alert">
            <div>
                <svg xmlns=
"http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" width="24" height="24">
                    <path stroke-linecap=
"round" stroke-linejoin="round" d="M14.857 17.082a23.848 23.848 0 0 0 5.454-1.31A8.967 8.967 0 0 1 18 9.75V9A6 6 0 0 0 6 9v.75a8.967 8.967 0 0 1-2.312 6.022c1.733.64 3.56 1.085 5.455 1.31m5.714 0a24.255 24.255 0 0 1-5.714 0m5.714 0a3 3 0 1 1-5.714 0M3.124 7.5A8.969 8.969 0 0 1 5.292 3m13.416 0a8.969 8.969 0 0 1 2.168 4.5" />
                </svg>  
                <span class=
"align-middle" th:text="${session.message}"></span>
            </div>

            <button type=
"submit" class="btn-close" data-bs-dismiss="alert" aria-label="Cerrar"></button>
        </div>
    </form>
</div>

要將此片段包含在我想要顯示通知的檢視中,我使用:

<div th:replace="~{fragments/messageAlert :: messageAlert}"></div>

透過 th:if="${session.message != ''}",我可以確定是否顯示該程式碼段。如果名為 message 的會話變數值為空,則不顯示任何內容,否則將顯示該程式碼段。

透過 <input type="hidden" th:name="returnUrl" th:value="${returnUrl}"> 我透過表單響應向控制器指出,在執行操作後我應該去哪個頁面。這將允許我在處理完資訊刪除後返回到原來的頁面。我使用 returnUrl 來通用地指示路徑,是因為它在呼叫表單時非常有用,例如,在本例中,我想知道表單內容傳送完畢後我應該去哪裡。例如,如果我們的應用程式要處理影院,並且要新增、編輯或刪除影院,那麼使用 returnUrl,我就可以告訴 @PostMapping 方法在執行這些操作後要顯示哪個檢視。為了做到這一點,當我使用建立、編輯或刪除檢視時,我在模型中新增了這個變數:
model.addAttribute("returnUrl", "cinemas");

其中,cinemas 是處理完我們要去的路徑的形式後要到達的路徑。舉個例子

 @GetMapping(<font>"/create")
    public String createForm(Model model) {

        model.addAttribute(
"cinema", new Cinema());
        
        model.addAttribute(
"returnUrl", "cinemas");

        return
"cinema/cinema-form";
    }

讓我們繼續。透過 th:classappend="${session.messageType} == 'danger'? 'alert-danger' : 'alert-info'" role="alert,我可以確定要顯示哪種型別的 Boostrap 警報,是用於通知(藍色背景框)還是用於提示錯誤(紅色背景框)。這是透過 messageType 會話變數實現的。

在警報中顯示 SVG 圖示後,在 span 標記內使用 th:text="${session.message}" 加入通知資訊。

透過 type="submit "按鈕,我將資訊表單傳送到控制器,控制器將按照表單中指定的路徑刪除資訊:<form th:action="@{{/message}" method="POST">。

初始化訊息和訊息型別變數
如前所述,我將使用 http 會話變數來儲存訊息內容和訊息型別。為此,我建立了一個會話過濾器,用於檢查每個 http 請求是否存在會話以及會話變數是否已建立。

我在 /Utils/SessionFilter.java 中包含了以下元件:

@Component
public class SessionFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {}

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {

        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;

        HttpSession session = httpRequest.getSession(true);

        if (session!= null) {
            if (session.getAttribute(<font>"message") == null)
                session.setAttribute(
"message", "");

            if (session.getAttribute(
"messageType") == null)
                session.setAttribute(
"messageType", ""); 
               
// 值:"危險 "或其他 ="資訊"。<i>
        }

        chain.doFilter(request, response);
    }

    @Override
    public void destroy() {}
}


透過 HttpSession session = httpRequest.getSession(true); 我獲得了會話,如果它不存在,我就建立它,因為我在 .getSession(true) 中包含了 true 值。

如果會話存在(如果沒有錯誤,應該已經存在),我將檢查 message 和 messageType 變數是否已初始化,如果沒有,我將它們初始化為""。

之後,我們就可以使用 session.getAttribute() 和 session.setAttribute() 從服務或控制器中讀取和更新會話變數的值了。

使用資訊
讓我們設想一個儲存表單內容以建立影院的服務。它看起來是這樣的

@Slf4j
@RequiredArgsConstructor
@Service
public class CinemaServiceImpl implements ICinemaService {

    private final CinemaRepository cinemaRepo;

    <font>// 該類的其他屬性和方法<i>

    @Override
    @Transactional
    public Cinema save(Cinema cinema) {
        log.info(
"save {}", cinema);

        try {
            return cinemaRepo.save(cinema);

        } catch (DataIntegrityViolationException e) {
            log.error(
"Error al guardar el cine: ", e);

            return null;
        }
    }
}

如您所見,CinemaServiceImpl 類實現了 ICinemaService 介面,注入了 CinemaRepository,並有一個儲存電影的方法。 電影從控制器傳送到儲存方法,該方法返回儲存的電影,如果發生錯誤,則返回空。

下面是該方法如何更改以包含成功或錯誤資訊:

@Slf4j
@RequiredArgsConstructor
@Service
public class CinemaServiceImpl implements ICinemaService {

    private final HttpSession session;
    private final CinemaRepository cinemaRepo;

    <font>//該類的其他屬性和方法<i>

    @Override
    @Transactional
    public Cinema save(Cinema cinema) {
        log.info(
"save {}", cinema);

        try {
            Cinema newCinema = cinemaRepo.save(cinema);

            String message =
"Cine " + newCinema + " guardado correctamente.";

            session.setAttribute(
"message", message);
            session.setAttribute(
"messageType", "info");

            return newCinema;

        } catch (DataIntegrityViolationException e) {
            log.error(
"Error al guardar el cine: ", e);

            session.setAttribute(
"message", "El cine no ha podido guardarse.");
            session.setAttribute(
"messageType", "danger");

            return null;
        }
    }
}

  • 如果使用 session.setAttribute("message",message),訊息變數的值就會改變;
  • 如果使用 session.setAttribute("messageType",message),訊息變數的值就會改變。
  • 出錯時也是如此。

請注意:在 String message = "Cinema " + newCinema + " successfully saved." 中,請確保 Cinema 實體覆蓋了 (@Override) toString() 方法,以便使用該方法建立字串。

關閉通知
上述通知將在當前檢視和模板中包含 messageAlert.html 程式碼段的所有檢視中顯示,直至該訊息不復存在,即訊息為""。請記住,要顯示該程式碼段,session.message 的內容必須不是""。

由於會話是從後臺管理的,因此有必要透過允許我們更改訊息變數值的路由來管理會話。

我在控制器資料夾中建立了以下 MessageController 控制器:

@Controller
public class MessageController {

    @PostMapping(<font>"/message")
    public String messageAlert(@RequestParam(value =
"returnUrl", required = false) String returnUrl,
                                    HttpSession session) {

        session.setAttribute(
"message", "");

        if (!stringIsEmpty(returnUrl)) {
            return
"redirect:" + returnUrl;
        } else {
            return
"redirect:/";
        }
    }

}

透過 /message 路徑,我可以使用:session.setAttribute("message", ""); 更改 message 的值,然後如果我們傳遞了 returnUrl 引數,就會返回到所需的路徑。

請記住,在建立片段時,我們定義了 <form th:action="@{/message}" method="POST"> 通知表單指向的路徑,點選 X 關閉通知時,表單就會傳送。

在安全配置中定義 /message 路徑非常重要。如果我們使用的是 Spring Security,SecurityConfig.class 中應包含類似內容:

 @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http)  throws Exception {
        http
            .authorizeHttpRequests(authRequest -> authRequest

                <font>// 其他配置<i>

                .requestMatchers(HttpMethod.POST,
"/message").permitAll()

               
// 其他配置<i>

                .anyRequest().authenticated()
            )

           
// 其他配置<i>

            return http.build();
    }

使用 .requestMatchers(HttpMethod.POST, "/message").permitAll() 後,路徑 /message 現在可見了。

如果通知是在執行只有使用者透過身份驗證後才能使用的程序時發出的,那麼在登出時,最好也用類似下面的方法清除通知:

@RequestMapping(<font>"/logout")
    public String logout(HttpSession session, Model model) {

        SecurityContextHolder.getContext().setAuthentication(null);

        model.addAttribute(
"returnUrl", "/");

        session.setAttribute(
"message", "");

        return
"logout";
    }

總結一下
建立所詳述的通知:

  1. 建立一個片段,在檢視中包含訊息文字。
  2. 初始化包含訊息和訊息型別的會話變數。
  3. 更新訊息變數的值,以便顯示它們。
  4. 顯示訊息後,從後臺刪除訊息。

原始碼:DAW Github 儲存庫

相關文章