Spring Security 6.x 系列(12)—— Form表单认证登录注销自定义配置

一、前言

在本系列文章中介绍了 Form 表单认证和注销流程,对部分源码也进行详细分析。

本章主要学习 Spring Security 中表单认证登录注销的相关自定义配置。

二、自定义登录页面

Spring Security 表单认证默认规则中对未认证的请求会重定向到默认登录页面,也支持自定义设置登录页面。

2.1 整合 Thymeleaf 模版引擎

2.1.1 pom 依赖

完整 pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><artifactId>spring-security</artifactId><groupId>com.gm</groupId><version>0.0.1-SNAPSHOT</version></parent><artifactId>form-security-custom</artifactId><packaging>jar</packaging><description>form表单登录示例(自定义)</description><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><dependency><groupId>org.thymeleaf.extras</groupId><artifactId>thymeleaf-extras-springsecurity6</artifactId></dependency><dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-test</artifactId><scope>test</scope></dependency></dependencies>
</project>

2.1.2 Thymeleaf 配置

src/main/resources/application.yaml

server:port: 9000logging:level:org.springframework.security: TRACEspring:thymeleaf:#配置模板路径,默认是templatesprefix: classpath:/templates/#文件后缀suffix: .html#编码encoding: UTF-8#内容类别content-type: text/html#模板的模式,支持 HTML, XML TEXT JAVASCRIPTmode: HTML#开发时建议设置为false,否则会有缓存,导致页面没法及时看到更新后的效果。cache: false

2.2 创建登录页

src/main/resources/templates 目录下创建一个简单的登录页 login.html

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>登录</title><!-- 引入 Bootstrap 样式文件 --><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css"><style>body {background-color: #f8f9fa;}.login-container {max-width: 400px;margin: 0 auto;margin-top: 100px;padding: 20px;border-radius: 5px;box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);background-color: #fff;}.custom-alert {background-color: #FFD2D2;padding: 10px;border-radius: 5px;display: flex;align-items: center;justify-content: center;text-align: center;min-height: 50px; /* 设置最小高度 */}</style>
</head>
<body>
<div class="container"><div class="row justify-content-center"><div class="col-md-6 login-container"><h2 class="text-center mb-4">Login</h2><form th:action="@{/login}" method="post"><div th:if="${exception}" class="custom-alert"><p th:text="${exception}"></p></div><div th:if="${logout}" class="custom-alert"><p th:text="${logout}"></p></div><div class="mb-3"><input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/><label for="username" class="form-label">用户名</label><input type="text" class="form-control" id="username" name="username" required></div><div class="mb-3"><label for="password" class="form-label">密码</label><input type="password" class="form-control" id="password" name="password" required></div><button type="submit" class="btn btn-primary w-100">Login</button></form></div></div>
</div><!-- 引入 Bootstrap JS 文件 -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
  • <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/> 是处理开启 CSRF 保护所需参数
  • <div th:if="${exception}" class="custom-alert"><p th:text="${exception}"></p></div> 是显示认证相关异常,稍后详细介绍
  • <div th:if="${logout}" class="custom-alert"><p th:text="${logout}"></p></div> 是显示注册相关信息,稍后详细介绍

2.3 创建 Controller 访问控制

新建 LoginController.java

@Controller
@Slf4j
class LoginController {/*** 登录页面** @return*/@GetMapping("/login")String login() {return "login";}
}

2.4 Spring Security 配置

@Configuration
@EnableWebSecurity(debug = true)
public class SecurityConfig {/*** 构建SecurityFilterChain** @param http* @return* @throws Exception*/@BeanSecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {// 配置所有http请求必须经过认证http.authorizeHttpRequests(authorizeRequests ->authorizeRequests.requestMatchers(new String[]{"/favicon.ico", "/custom/error", "/error", "/logout/success"}).permitAll().anyRequest().authenticated());// 开启表单认证(默认配置)// http.formLogin(Customizer.withDefaults());// 表单认证自定义配置http.formLogin(form ->form.loginPage("/login").permitAll() // /login,需permitAll放开访问控制并配置对应的controller请求地址.loginProcessingUrl("/login") // form表单登录处理请求URL(POST).usernameParameter("username") // form表单用户名参数名称.passwordParameter("password") // form表单密码参数名称);// 开启 CSRF 保护http.csrf(Customizer.withDefaults());// 禁止 CSRF 保护// http.csrf(csrf -> csrf.disable());// 构造器构建SecurityFilterChain对象return http.build();}/*** 配置登录名密码** @return*/@Beanpublic UserDetailsService userDetailsService() {UserDetails user = User.withUsername("admin").password("{noop}123").roles("USER").build();return new InMemoryUserDetailsManager(new UserDetails[]{user});}
}
  • loginPage("/login").permitAll():配置 URL 生成登录页面,常与 Controller 配合使用,必须是 POST
  • loginProcessingUrl("/login"):配置登录请求处理 URL ,用于修改UsernamePasswordAuthenticationFilter中拦截登录处理请求 URL 地址。
  • usernameParameter("username"):配置用户名参数名称 ,用于修改UsernamePasswordAuthenticationFilter中拦截登录处理请求用户名对应的参数名称。
  • passwordParameter("password"):配置密码参数名称 ,用于修改UsernamePasswordAuthenticationFilter中拦截登录处理请求密码对应的参数名称。

2.5 效果

在这里插入图片描述

三、自定义登录成功请求转发/重定向地址

3.1 创建登录成功页

src/main/resources/templates 目录下创建一个简单的登录成功页面 success.html

3.2 创建 Controller 访问控制

LoginController.java 新增:

/*** 登录成功页面** @return*/
@RequestMapping("/login/success")
String success() {return "success";
}

3.3 Spring Security 配置

@Configuration
@EnableWebSecurity(debug = true)
public class SecurityConfig {/*** 构建SecurityFilterChain** @param http* @return* @throws Exception*/@BeanSecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {// 配置所有http请求必须经过认证http.authorizeHttpRequests(authorizeRequests ->authorizeRequests.requestMatchers(new String[]{"/favicon.ico", "/custom/error", "/error", "/logout/success"}).permitAll().anyRequest().authenticated());// 开启表单认证(默认配置)// http.formLogin(Customizer.withDefaults());// 表单认证自定义配置http.formLogin(form ->form.loginPage("/login").permitAll() // /login,需permitAll放开访问控制并配置对应的controller请求地址.loginProcessingUrl("/login") // form表单登录处理请求URL(POST).usernameParameter("username") // form表单用户名参数名称.passwordParameter("password") // form表单密码参数名称//.successForwardUrl("/login/success") // 登录成功请求转发URL(请求转发地址栏不变).defaultSuccessUrl("/login/success") // 登录成功请求重定向URL(重定向地址栏变));// 开启 CSRF 保护http.csrf(Customizer.withDefaults());// 禁止 CSRF 保护// http.csrf(csrf -> csrf.disable());// 构造器构建SecurityFilterChain对象return http.build();}/*** 配置登录名密码** @return*/@Beanpublic UserDetailsService userDetailsService() {UserDetails user = User.withUsername("admin").password("{noop}123").roles("USER").build();return new InMemoryUserDetailsManager(new UserDetails[]{user});}
}
  • successForwardUrl("/login/success"):配置登录成功请求转发 URL (请求转发地址栏不变),常与 Controller 配合使用,详情请见:ForwardAuthenticationSuccessHandler

  • defaultSuccessUrl("/login/success"):配置登录成功请求重定向URL(重定向地址栏变),常与 Controller 配合使用,详情请见:SavedRequestAwareAuthenticationSuccessHandler

3.4 效果

3.4.1 请求转发效果

在这里插入图片描述

3.4.2 重定向效果

在这里插入图片描述

四、自定义登录失败请求转发/重定向地址

4.1 创建登录失败页

登录失败页采用原登录页,显示各类登录异常。

4.2 创建 Controller 访问控制

/*** 登录失败页面** @return*/
@RequestMapping("/custom/error")
String failure(HttpServletRequest request, Model model) {// 以下是配置failureForwardUrl方式获取登录异常Object exception = request.getAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);if (exception != null) {if (exception instanceof BadCredentialsException) {BadCredentialsException badCredentialsException = (BadCredentialsException) exception;model.addAttribute("exception", badCredentialsException.getMessage());return "login";}}exception = request.getAttribute(WebAttributes.ACCESS_DENIED_403);if (exception instanceof AccessDeniedException) {AccessDeniedException accessDeniedException = (AccessDeniedException) exception;model.addAttribute("exception", accessDeniedException.getMessage());return "login";}// 以下是配置failureUrl方式获取登录异常HttpSession session = request.getSession(false);if (session != null) {exception = request.getSession().getAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);model.addAttribute("exception", ((AuthenticationException) exception).getMessage());return "login";}return "login";
}

注意
根据 Spring Security 中失败请求处理的配置不同获取异常的方式也是多样的。

4.3 Spring Security 配置

@Configuration
@EnableWebSecurity(debug = true)
public class SecurityConfig {/*** 构建SecurityFilterChain** @param http* @return* @throws Exception*/@BeanSecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {// 配置所有http请求必须经过认证http.authorizeHttpRequests(authorizeRequests ->authorizeRequests.requestMatchers(new String[]{"/favicon.ico", "/custom/error", "/error", "/logout/success"}).permitAll().anyRequest().authenticated());// 开启表单认证(默认配置)// http.formLogin(Customizer.withDefaults());// 表单认证自定义配置http.formLogin(form ->form.loginPage("/login").permitAll() // /login,需permitAll放开访问控制并配置对应的controller请求地址.loginProcessingUrl("/login") // form表单登录处理请求URL(POST).usernameParameter("username") // form表单用户名参数名称.passwordParameter("password") // form表单密码参数名称//.successForwardUrl("/login/success") // 登录成功请求转发URL(请求转发地址栏不变).defaultSuccessUrl("/login/success") // 登录成功请求重定向URL(重定向地址栏变).failureForwardUrl("/custom/error") // 登录失败请求转发URL(请求转发地址栏不变)//.failureUrl("/custom/error") // 登录失败请求重定向URL(重定向地址栏变)(POST));// 配置AccessDeniedException异常处理请求URL(POST),主要是是处理401 BadCredentialsException 和 403 AccessDeniedException 异常http.exceptionHandling(exception -> exception.accessDeniedPage("/custom/error"));// 开启 CSRF 保护http.csrf(Customizer.withDefaults());// 禁止 CSRF 保护// http.csrf(csrf -> csrf.disable());// 构造器构建SecurityFilterChain对象return http.build();}/*** 配置登录名密码** @return*/@Beanpublic UserDetailsService userDetailsService() {UserDetails user = User.withUsername("admin").password("{noop}123").roles("USER").build();return new InMemoryUserDetailsManager(new UserDetails[]{user});}
}
  • failureForwardUrl("/custom/error"):配置登录失败跳转请求 URL ,常与 Controller 配合使用,详情请见:ForwardAuthenticationFailureHandler

  • failureUrl("/custom/error"):配置登录失败请求重定向URL(重定向地址栏变),常与 Controller 配合使用,详情请见:SimpleUrlAuthenticationFailureHandler

  • http.exceptionHandling(exception -> exception.accessDeniedPage("/custom/error")):配置 AccessDeniedException 异常处理调整URL,主要是是处理401 BadCredentialsException403 AccessDeniedException 异常,详情请见:AccessDeniedHandlerImpl

4.4 效果

在这里插入图片描述
在这里插入图片描述

五、自定义登录成功/失败处理器

目前基本都是前后端分离,基于 JSON 实现交与,后端中并没有任何页面,也不需要跳转地址,只需要告知前端登录成功返回用户信息即可,然后由前端进行页面跳转。

5.1 自定义登录成功处理器

在上篇录流程分析过,登录成功后会调用登录成功处理器(默认SavedRequestAwareAuthenticationSuccessHandler)进行页面跳转,那么只需要自定义登录成功处理器,就可以直接实现 JSON 返回,AuthenticationsuccessHandler 接口用于处理用户身份验证成功后的处理策略,实现可以随心所欲:

public class JsonAuthenticationSuccessHandler implements AuthenticationSuccessHandler {/*** 登录成功后直接返回 JSON** @param request        请求* @param response       响应* @param authentication 成功认证的用户信息*/@Overridepublic void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {response.setContentType("application/json;charset=utf-8"); // 返回JSONresponse.setStatus(HttpStatus.OK.value());  // 状态码 200Map<String, Object> result = new HashMap<>(); // 返回结果result.put("msg", "登录成功");result.put("code", 200);result.put("data", authentication);response.getWriter().write(JSONUtil.toJsonStr(result));}
}

5.2 自定义登录失败处理器

在上篇录流程分析过,登录失败后会调用登录失败处理器(默认SimpleUrlAuthenticationFailureHandler)进行页面跳转,那么只需要自定义登录失败处理器,就可以直接实现 JSON 返回,AuthenticationFailureHandler 接口用于处理用户身份验证成功后的处理策略,实现可以随心所欲:

public class JsonAuthenticationFailureHandler implements AuthenticationFailureHandler {@Overridepublic void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {response.setContentType("application/json;charset=utf-8"); // 返回JSONresponse.setStatus(HttpStatus.BAD_REQUEST.value());  // 状态码 400Map<String, Object> result = new HashMap<>(); // 返回结果result.put("msg", "登录失败");result.put("code", 400);result.put("data", exception.getMessage());response.getWriter().write(JSONUtil.toJsonStr(result));}
}

六、自定义注销配置

6.1 创建注销确认页

src/main/resources/templates 目录下创建一个简单的登录成功页面 logout_confirm.html

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Logout Confirmation</title><!-- 引入 Bootstrap 样式文件 --><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css">
</head>
<body>
<div class="container mt-5"><div class="row justify-content-center"><div class="col-md-6"><div class="card"><div class="card-header bg-danger text-white"><h4 class="mb-0">确认注销</h4></div><div class="card-body" sec:authorize="${isAuthenticated()}"><p>您确定要注销吗?</p><p>用户名: <span th:text="${#authentication.name}"></span></p><div sec:authorize="${isAuthenticated()}"><form th:action="@{/logout}" method="post"><input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/><a href="/login/success" class="btn btn-success me-2">取消</a><input type="submit" class="btn btn-danger" value="确认注销" /></form></div></div></div></div></div>
</div><!-- 引入 Bootstrap JS 文件 -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

6.2 创建 Controller 访问控制

/*** 注销成功页面** @return*/
@RequestMapping("/logout/success")
String logoutSuccess(HttpServletRequest request, Model model) {model.addAttribute("logout", "您已登出");return "login";
}/*** 注销确认页面** @return*/
@RequestMapping("/custom/logout")
String logoutConfirm(HttpServletRequest request, Model model) {return "logout_confirm";
}

6.3 Spring Security 配置

@Configuration
@EnableWebSecurity(debug = true)
public class SecurityConfig {/*** 构建SecurityFilterChain** @param http* @return* @throws Exception*/@BeanSecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {// 配置所有http请求必须经过认证http.authorizeHttpRequests(authorizeRequests ->authorizeRequests.requestMatchers(new String[]{"/favicon.ico", "/custom/error", "/error", "/logout/success"}).permitAll().anyRequest().authenticated());// 开启表单认证(默认配置)// http.formLogin(Customizer.withDefaults());// 表单认证自定义配置http.formLogin(form ->form.loginPage("/login").permitAll() // /login,需permitAll放开访问控制并配置对应的controller请求地址.loginProcessingUrl("/login") // form表单登录处理请求URL(POST).usernameParameter("username") // form表单用户名参数名称.passwordParameter("password") // form表单密码参数名称.successForwardUrl("/login/success") // 登录成功请求转发URL(请求转发地址栏不变)//.defaultSuccessUrl("/login/success") // 登录成功请求重定向URL(重定向地址栏变).failureForwardUrl("/custom/error") // 登录失败请求转发URL(请求转发地址栏不变)//.failureUrl("/custom/error") // 登录失败请求重定向URL(重定向地址栏变));// 配置AccessDeniedException异常处理调整URL,主要是是处理401 BadCredentialsException 和 403 AccessDeniedException 异常http.exceptionHandling(exception -> exception.accessDeniedPage("/custom/error"));// 登出自定义配置http.logout(logout -> logout.logoutSuccessUrl("/logout/success") // 自定义注销成功后跳转请求URL(POST)//.logoutUrl("/logout") // 自定义单个注销处理请求URL(开启CSRF保护时POST,关闭开启CSRF保护时POST GET PUT DELETE).logoutRequestMatcher(new OrRequestMatcher(new AntPathRequestMatcher("/logout","POST"), // 未配置 LogoutSuccessHandler 时,执行默认的 LogoutSuccessHandlernew AntPathRequestMatcher("/logout2","GET"),new AntPathRequestMatcher("/logout2","GET"))) // 自定义注销处理请求拦截组合.defaultLogoutSuccessHandlerFor(new LogoutSuccessHandler() { // 通过添加多个来实现从不同注销处理请求URL退出执行不同的逻辑@Overridepublic void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {response.setContentType("application/json;charset=UTF-8");Map<String, Object> result = new HashMap<String, Object>();result.put("status", 200);result.put("msg", "使用logout1注销成功!");ObjectMapper om = new ObjectMapper();String s = om.writeValueAsString(result);response.getWriter().write(s);}}, new AntPathRequestMatcher("/logout1", "GET")).defaultLogoutSuccessHandlerFor(new LogoutSuccessHandler() {@Overridepublic void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {response.setContentType("application/json;charset=UTF-8");Map<String, Object> result = new HashMap<String, Object>();result.put("status", 200);result.put("msg", "使用logout2注销成功!");ObjectMapper om = new ObjectMapper();String s = om.writeValueAsString(result);response.getWriter().write(s);}}, new AntPathRequestMatcher("/logout2", "GET")).addLogoutHandler(new LogoutHandler() {@Overridepublic void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {System.out.println("-----自定义注销处理器------");}}));// 开启 CSRF 保护http.csrf(Customizer.withDefaults());// 禁止 CSRF 保护// http.csrf(csrf -> csrf.disable());// 构造器构建SecurityFilterChain对象return http.build();}/*** 配置登录名密码** @return*/@Beanpublic UserDetailsService userDetailsService() {UserDetails user = User.withUsername("admin").password("{noop}123").roles("USER").build();return new InMemoryUserDetailsManager(new UserDetails[]{user});}
}
  • logoutUrl("/logout"):配置单个登录请求处理 URL ,用于修改LogoutFilter中拦截登录处理请求 URL 地址。

    在上文介绍过:开启CSRF保护时匹配POST请求类型,关闭开启CSRF保护时匹配POSTGETPUTDELETE请求类型。

  • logoutRequestMatcher(...) :配置组合式请求处理匹配规则,用于不同的登录请求处理匹配不同的处理规则。
  • defaultLogoutSuccessHandlerFor(...) :配置基于不同的登录请求处理匹配不同的注销成功处理器
  • addLogoutHandler(...):配置添加自定义注销处理器

    自定义注销处理器会添加到执行首位,并不会删除默认添加的处理器:。

  • 自定义清理项:
    • clearAuthentication(true):清理Authentication ,默认true

    • deleteCookies("",""):删除某些指定 cookie

    • invalidateHttpSession(true):设置当前登录用户Session(保存登录后的用户信息)无效,默认true

6.4 效果

6.4.1 登录确认效果

在这里插入图片描述
在这里插入图片描述

6.4.2 自定义注销成功处理器效果

在这里插入图片描述

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

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

相关文章

中奖名单模板_春分纪中奖名单出炉~才艺主公用“树叶阵法”致敬率土!

在上周发起的“率土春分纪”活动里小率每天都能在后台中收获主公们的“春天故事”今天小率将优质投稿整理出来分享给各位主公1春分诗词春赋雨水以将兮&#xff0c;天微微复日暖&#xff1b;春分之临兮&#xff0c;地脉脉而野苏。美好触手可及&#xff0c;万事恰逢转机。且看柳嫩…

JDK 10的摘要Javadoc标签

JDK 10通过发行JDK-8173425 引入了Javadoc标签{summary} &#xff08;“ Javadoc需要一个新标签来指定摘要。”&#xff09;。 这个新标签允许开发人员显式指定Javadoc注释的哪些部分出现在“摘要”中&#xff0c;而不是依靠Javadoc的默认处理来寻找一个时间和空间来划定注释的…

用c语言写代码_如何避免用动态语言的思维写Go代码

由于招聘市场上Go工程师的供给量不足&#xff0c;所以在招人的时候我们招了不少愿意转型用Go语言进行开发的PHP工程师&#xff0c;不过虽说换了个语言&#xff0c;在他们代码的时候还是能发现很多PHP的影子。if语句后面非要带括号这种问题就不说了&#xff0c;这属于不懂事&…

java ee cdi_Java EE CDI依赖关系消歧示例

java ee cdi在本教程中&#xff0c;我们将向您展示如何避免CDI bean中的依赖关系消除歧义。 在CDI中&#xff0c;我们可以为应用程序中不同客户端的接口的多个实现实现依赖项注入。 依赖性消除歧义的问题是客户端如何在不同的实现中调用特定的实现&#xff0c;而不会发生任何错…

tfw文件如何导入cad_教你三维家3d设计软件如何导入cad文件

一、导入CAD户型图的&#xff0c;注意点&#xff1a;CAD图要求&#xff1a;除墙体外&#xff0c;其它线段不可出现&#xff0c;(如&#xff1a;门、柱子、窗、阳台、标注、家具、植物、摆件等)且墙体线条要闭合&#xff0c;保存DXF格式。第一步:点击户型图--本地上传CAD图。第二…

用EnumMaps映射枚举键

这是一种在JDK中存在很长时间的类型&#xff0c;当我们要定义以枚举类型作为键的映射时会派上用场&#xff1a; EnumMap是一种特殊的Map 。 我们将为给定的枚举创建一个映射&#xff1a; public enum CoffeeType {ESPRESSO, POUR_OVER, FRENCH_PRESS }EnumMap在创建时需要注意…

同级选择器_10-CSS3选择器详解

CSS3在CSS2基础上&#xff0c;增强或新增了许多特性&#xff0c; 弥补了CSS2的众多不足之处&#xff0c;使得Web开发变得更为高效和便捷。CSS3的现状浏览器支持程度不够好&#xff0c;有些需要添加私有前缀移动端支持优于PC端不断改进中应用相对广泛应对的策略&#xff1a;渐进…

机器人庄园作文_十年后的家乡作文精选8篇

十年后的家乡作文精选8篇十年后的家乡作文&#xff1a;十年后的家乡十年前我的家乡美丽富饶&#xff0c;家前的小溪清澈见底&#xff0c;小溪妹妹还&#xff1a;“哗啦啦”的唱起了欢乐的歌谣。树木葱葱茏茏&#xff0c;花朵都露出了美丽可爱的笑脸&#xff0c;蝴蝶、蜜蜂、小鸟…

java heroku_Heroku和Java –从新手到初学者,第2部分

java heroku问题 所以过了几天&#xff0c;我可以回到我的Recaps小项目。 我从检查日志开始&#xff0c;发现了以下内容&#xff1a; 2012-03-04T01:52:5100:00 heroku[web.1]: Idling 2012-03-04T01:52:5300:00 heroku[web.1]: Stopping process with SIGTERM 2012-03-04T01:…

存储限制_明年6月份开始,谷歌相册将终止免费无限存储服务

站长之家(ChinaZ.com)11月12日 消息:在提供免费服务5年后&#xff0c;谷歌对外宣布将终止提供无限容量免费照片存储服务&#xff0c;转而只提供的15GB免费存储空间&#xff0c;超过部分就需要向谷歌付费。这一变化将于2021年6月1日生效&#xff0c;这意味着如果用户上传的照片超…

下载 沙耶之歌Android_沙耶之歌安卓版apk-沙耶之歌下载手机版v1.2-飘荡下载

一款超经典的日式ADV游戏&#xff0c;游戏中有着精致的动漫风格画风&#xff0c;并且讲述了一个非常重口味但又异常纯洁的恋爱故事&#xff0c;玩家将会扮演男主进行游戏&#xff0c;超级丰富精彩的剧情等你来体验&#xff0c;并且还有着不同的剧情选项可以选择&#xff0c;能否…

Maven,Eclipse和Java 9

任何在eclipse中使用M2E&#xff08;maven-to-eclipse&#xff09;插件的人都知道您在哪里运行构建的问题&#xff0c;然后在项目上更新maven只是让它重置JRE并抛出一堆项目错误&#xff01; 我在使用Open Liberty与Java 9一起运行Java EE 8的帖子中注意到了这个问题 解决方案…

python变量持久化_Python 数据持久化:JSON

Python 数据持久化&#xff1a;JSON编程派微信号&#xff1a;codingpy淡蓝色字体可以直接点击查看上周更新的《Think Python 2e》第14章讲述了几种数据持久化的方式&#xff0c;包括dbm、pickle等&#xff0c;但是考虑到篇幅和读者等因素&#xff0c;并没有将各种方式都列全。本…

jwt配置 restful_SpringBoot实现JWT保护前后端分离RESTful API

本文将用不到100行Java代码, 教你如何在Spring Boot里面用JWT保护RESTful api.登录前登录之后即可得到正确结果登陆后1. 什么是JWT了解JWT的同学可以跳过这一部分废话少说, 我们先看看什么是JWT. JSON Web Token其实就是一个包含认证数据的JSON, 大概长这样子分三个部分,第一部…

fusion构建器代码语法_构建器模式:适用于代码,适用于测试

fusion构建器代码语法我发现构建器设计模式偶尔在代码中有用&#xff0c;但在测试中经常有用。 本文简要概述了该模式&#xff0c;然后介绍了在测试中使用该模式的一个有效示例。 请参阅github中的代码。 生成器模式的背景 根据GoF的书 &#xff0c;构建器设计模式用于“将复杂…

6000毫安以上智能手机_三星超长续航神机,6000毫安+128GB,上市半年不到跌至1499...

现在的手机是越来越智能了&#xff0c;无论是苹果还是安卓&#xff0c;基本都能为用户的生活添加几分乐趣&#xff0c;因为&#xff0c;当我们感到无聊时&#xff0c;基本都可以通过智能手机来打发时间。据我所知&#xff0c;不少人在用智能手机时都有个困扰&#xff0c;就是续…

使用RabbitMQ进行消息传递

RabbitMQ是一个强大的消息代理&#xff0c;可用于实现不同的消息传递模式。 即使有出色的教程 &#xff08;使用不同的语言和框架&#xff09;&#xff0c;也很难理解这些概念。 在这篇文章中&#xff0c;我想展示一些可以用RabbitMQ实现的不同范例&#xff0c;以及为什么要为某…

android 为什么fragment在调用hide方法后没有生效_Android 多 Fragment 切换优化

code小生,一个专注 Android 领域的技术平台公众号回复 Android 加入我的安卓技术群作者&#xff1a;DDDong丶链接&#xff1a;https://www.jianshu.com/p/c8e8a0249911声明&#xff1a;本文已获DDDong丶授权发表&#xff0c;转发等请联系原作者授权问题分析一直在简书里看别人的…

mysql如何查看远程用户_MySQL系列(十)--用户权限及远程访问

本文基于MySQL8.0&#xff0c;记录一下完整的远程访问的过程&#xff0c;以及这个过程中可能遇到的问题&#xff0c;MySQL运行在阿里云服务器&#xff0c;操作系统&#xff1a;CentOS 7.6 64位顺便说下&#xff0c;买服务器还是要双十二这种拉新活动再买&#xff0c;用一个新的…

spring mvc拆分_Spring集成–强大的拆分器聚合器

spring mvc拆分健壮是什么意思&#xff1f; 在本文的上下文中&#xff0c;健壮性是指在不立即返回给调用者的情况下管理流中的异常条件的能力。 在某些处理方案中&#xff0c; n个 m个回答足以做出结论。 通常具有这些趋势的示例处理场景是&#xff1a; 财务&#xff0c;保…