Spring Boot中实现Thymeleaf通知

当应用程序执行潜在的关键操作(例如编辑、保存或从数据库中删除数据)时,建议通知用户操作的成功或失败。这篇文章介绍了如何在 Thymeleaf 模板中显示通知的基本解决方案。

假设读者具备 Spring Boot、Thymeleaf 和 Bootstrap 的一般知识来应用这些内容。

这个想法是在应用程序中为通知分配一个显示区域,并仅在应该显示通知时激活该区域,让用户可以选择在阅读后关闭通知。该区域可以创建为片段并包含在所需的模板中,或者包含在与应用程序视图关联的所有模板中。

需要一个变量来充当显示或不显示这些通知的“键”。由于从控制器的每个路由管理器发送消息有些费力,因此我将使用 http 会话过滤器,在其中保存消息的值和要显示的消息类型。

最后,使用消息处理程序,我将管理消息的删除,以便它停止显示。

创建了一个名为的代码片段messageAlert.html,并将其包含在fragments资源模板的文件夹中。这是一个可以随意修改的示例,以使其设计适应将要使用的应用程序。

<div th:fragment="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>

要将此片段包含在我想要显示通知的视图中,我使用:

通过 th:if="${session.message != ''}",我可以确定是否显示该代码段。如果名为 message 的会话变量值为空,则不显示任何内容,否则将显示该代码段。

通过 我通过表单响应向控制器指出,在执行操作后我应该去哪个页面。这将允许我在处理完信息删除后返回到原来的页面。我使用 returnUrl 来通用地指示路径,是因为它在调用表单时非常有用,例如,在本例中,我想知道表单内容发送完毕后我应该去哪里。例如,如果我们的应用程序要处理影院,并且要添加、编辑或删除影院,那么使用 returnUrl,我就可以告诉 @PostMapping 方法在执行这些操作后要显示哪个视图。为了做到这一点,当我使用创建、编辑或删除视图时,我在模型中添加了这个变量: model.addAttribute("returnUrl", "cinemas");

其中,cinemas 是处理完我们要去的路径的形式后要到达的路径。举个例子

 @GetMapping("/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 "按钮,我将信息表单发送到控制器,控制器将按照表单中指定的路径删除信息:

初始化消息和消息类型变量 如前所述,我将使用 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("message") == null)
                session.setAttribute("message", "");

            if (session.getAttribute("messageType") == null)
                session.setAttribute("messageType", ""); 
                // 值:"危险 "或其他 ="信息"。
        }

        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;

    // 该类的其他属性和方法

    @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;

    //该类的其他属性和方法

    @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("/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 参数,就会返回到所需的路径。

请记住,在创建片段时,我们定义了 通知表单指向的路径,点击 X 关闭通知时,表单就会发送。

在安全配置中定义 /message 路径非常重要。如果我们使用的是 Spring Security,SecurityConfig.class 中应包含类似内容:

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

                // 其他配置

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

                // 其他配置

                .anyRequest().authenticated()
            )

            // 其他配置

            return http.build();
    }

使用 .requestMatchers(HttpMethod.POST, "/message").permitAll() 后,路径 /message 现在可见了。

如果通知是在执行只有用户通过身份验证后才能使用的进程时发出的,那么在注销时,最好也用类似下面的方法清除通知:

@RequestMapping("/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 存储库