自定义认证过滤器和自定义授权过滤器

目录

通过数据库动态加载用户信息

具体实现步骤

一.创建数据库

二.编写secutity配置类

三.编写controller

四.编写服务类实现UserDetailsService接口类

五.debug springboot启动类

认证过滤器

 SpringSecurity内置认证流程

 自定义认证流程

 第一步:自定义一个类继承AbstractAuthenticationProcessingFilter类

 第二步:把自定义的认证过滤器交给spring管理,并配置认证的路径

第三步:把自定义的认证过滤器配置在默认认证过滤器之前

 第四步:测试

 基于JWT实现无状态认证 

第一步:编写生成jwt票据工具类

 第二步:在认证成功后执行的方法里生成jwt令牌并返回给前端

第三步:结果

SpringSecurity基于Jwt实现认证小结 

授权过滤器

 授权流程

自定义授权过滤器流程

第一步:自定义一个类继承 OncePerRequestFilter类

 第二步:把这个授权过滤器交给spring管理

 第三步:测试


通过数据库动态加载用户信息

使用SpringSecurity时,访问某一个资源路径时,SpringSecurity会自动拦截,并跳转到登录页面(SpringSecurity提供),登录之后才可以访问指定的资源。

登录的这个过程就是用户的认证

认证的具体过程就是:Spring Security底层会自动调用UserDetailsService类型bean提供的用户信息前端返回的用户信息进行合法比对,如果比对成功则资源放行,否则就认证失败;

所以我们需要创建一个UserDetailsService对象bean给spring容器管理

具体的步骤就是需要创建一个类实现UserDetailsService接口,重写loadUserByUsername()方法,

UserDetails loadUserByUsername(String userName)

用户认证时自动调用这个方法,userName就是前端输入的明文密码

调用这个方法的目的就是加载一个用户类,让SpringSecurity底层拿去与前端返回的用户信息进行比对

在这个方法里面根据用户名去数据库查找这个用户的信息(用户名,用户密文密码,用户拥有的权限集合),然后封装到User类(实现了UserDetails接口)对象里,最后返回即可。

 返回的这个用户信息就会被SpringSecurity底层自动调用去与前端返回的用户信息进行合法比对

 总的来说就是:用户的认证是先调用loadUserByUsername方法,根据前端返回的用户名根据数据库查找这个用户的详细信息(用户名,用户密码密文,用户的权限集合),然后封装成一个UserDetails类,然后Spring Security底层再自动调用UserDetailsService类型bean提供的用户信息前端返回的用户信息进行合法比对,如果比对成功则资源放行,否则就认证失败;

用户类中的是用户的密文密码,而前端返回的用户密码是密文,SpringSecurity要怎么比对呢

所以我们要配置BCryptPasswordEncoder加密相关bean,底层会自动调用这个bean把密码密文与密码明文进行匹配,所以我们存入数据库的密码密文需要使用BCrypt算法加密

具体实现步骤

一.创建数据库

create database security_demo default charset=utf8mb4;
use security_demo;CREATE TABLE `tb_user` (`id` int(11) NOT NULL AUTO_INCREMENT,`username` varchar(100) DEFAULT NULL,`password` varchar(100) DEFAULT NULL,`roles` varchar(100) DEFAULT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;INSERT INTO `tb_user` VALUES (1, 'hhh', '$2a$10$f43iK9zKD9unmgLao1jqI.VluZ.Rr/XijizVEA73HeOu9xswaUBXC', 'ROLE_ADMIN,P5');
INSERT INTO `tb_user` VALUES (2, 'aaa', '$2a$10$f43iK9zKD9unmgLao1jqI.VluZ.Rr/XijizVEA73HeOu9xswaUBXC', 'ROLE_SELLER,P7,ROLE_ADMIN');

密码都是123456经过BCrypt算法加密后的

二.编写secutity配置类

@Configuration
@EnableWebSecurity//开启web安全设置生效
//开启SpringSecurity相关注解支持
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {//TODO:返回一个BCryptPasswordEncoder密码加密类类,让SpringSecurity自动调用把密码密文和明文进行匹配@Beanpublic PasswordEncoder passwordEncoder(){return new BCryptPasswordEncoder();}@Overrideprotected void configure(HttpSecurity http) throws Exception {http.formLogin()//开启默认form表单登录方式.and().logout()//登出用默认的路径登出 /logout.permitAll()//允许所有的用户访问登录或者登出的路径.and().csrf().disable()//启用CSRF,防止CSRF攻击.authorizeRequests();//授权方法,该方法后有若干子方法进行不同的授权规则处理/* //允许所有账户都可访问(不登录即可访问),同时可指定多个路径.antMatchers("/register").permitAll()//允许所有的用户访问.antMatchers("/hello").hasAuthority("P1") //具有P5权限才可以访问.antMatchers("/say").hasRole("SELECT") //具有ROLE_ADMIN 角色才可以访问,会自动加上ROLE_.antMatchers("/aa","/bb").hasAnyAuthority("P1","ROLE_SELECT")//有任意一个权限都可以访问.antMatchers("/aa","/bb").hasAnyRole("SELECT")//有任意一个权限都可以访问.antMatchers("/aa","/bb").hasIpAddress("192.168.xxx.xxx")//必须是192.168.地址才能访问.antMatchers("/aa","/bb").denyAll()//任何用户都不可以访问.anyRequest().authenticated(); //除了上边配置的请求资源,其它资源都必须授权才能访问*/}
}

三.编写controller

使用注解给每个资源接口授权,拥有指定权限才能进行访问

@RestController
public class UserController {//拥有ROLE_ADMIN权限的用户才能访问此接口@PreAuthorize("hasRole('ADMIN')")@GetMapping("/hello")public String hello(){return "hello security";}//拥有ROLE_SELECT权限的用户才能访问此接口@PreAuthorize("hasRole('SELECT')")@GetMapping("/say")public String say(){return "say security";}@PermitAll//任何用户都可以访问此接口,不需要进行认证@GetMapping("/register")public String register(){return "register security";}
}

四.编写服务类实现UserDetailsService接口类

注意:

1.User是UserDetails接口的实现类,封装了用户权限相关的的数据及用户的权限数据, 不要导错包 ;

2.工程已经配置好了BCryptPasswordEncoder加密相关bean,底层会自动调用;

/*** 创建一个UserDetailsService类bean,用户认证时自动调用loadUserByUsername方法加载用户信息* 之后SpringSecurity会使用这个用户类与前端返回的用户信息进行合法比对,成功才能放行资源*/
@Service("userDetailsService")
public class MyUserDetailsService implements UserDetailsService {@Autowiredprivate TbUserMapper tbUserMapper;/*** 通过用户名去数据库获取这个用户的具体信息(用户名,用户密文密码,用户的权限集合)创建一个用户类* @param userName 前端返回的用户名字* @return 返回一个用户类* @throws UsernameNotFoundException 用户不存在的异常*/@Overridepublic UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {//根据用户名去数据库查找用户的基本信息(用户名,密文密码,用户的权限集合)TbUser tbUser = tbUserMapper.findByUserName(userName);if(tbUser==null){throw new UsernameNotFoundException("该用户不存在");}//封装UserDetails类,User是UserDetails接口的实现类//使用工具类把用户的权限使用逗号进行分割List<GrantedAuthority> authorities = AuthorityUtils.commaSeparatedStringToAuthorityList(tbUser.getRoles());UserDetails user = User.builder().username(tbUser.getUsername()).password(tbUser.getPassword()).authorities(authorities).build();//返回这个用户类,之后SpringSecurity会使用这个用户类与前端的返回的用户信息进行比对//自动调用BCryptPasswordEncoder bean把用户明文密码与用户密文密码进行比对return user;}
}

五.debug springboot启动类

访问hello接口(拥有ROLE_ADMIN权限才能访问),会自动跳到springsecurity提供的用户认证界面

会跳转到loadUserByUsername方法,并获取前端的用户名,然后使用这个用户名创建一个用户类

 认证通过,即springSecurity会自动调用BCryptPasswordEncoder加密相关bean把密文密码和明文密码继续比对,比对通过就是认证通过,又因为hhh用户拥有ROLE_ADMIN权限,所以可以访问

认证过滤器

 SpringSecurity内置认证流程

核心流程梳理如下:

  • 认证过滤器(UsernamePasswordAuthentionFilter)接收form表单提交的账户、密码信息,并封装成UsernamePasswordAuthenticationToken认证凭对象;
  • 认证过滤器调用认证管理器AuthenticationManager进行认证处理;
  • 认证管理器通过调用用户详情服务获取用户详情UserDetails;
  • 认证管理器通过密码匹配器PasswordEncoder进行匹配,如果密码一致,则将用户相关的权限信息一并封装到Authentication认证对象中;
  • 认证过滤器将Authentication认证过滤器放到认证上下文,方便请求从上下文获取认证信息;

 自定义认证流程

SpringSecurity内置的认证过滤器是基于post请求且为form表单的方式获取认证数据的,那如何接收前端Json异步提交的数据据实现认证操作呢?

​ 显然,我们可仿照UsernamePasswordAuthentionFilter类自定义一个过滤器并实现认证过滤逻辑;

 第一步:自定义一个类继承AbstractAuthenticationProcessingFilter类

在这个类中我们要重写 attemptAuthentication()方法,在这个方法中把前端收到的用户名,用户明文密码封装成 UsernamePasswordAuthenticationToken票据对象中,然后调用认证管理器认证这个用户的信息(就是使用UserDetailsService返回的用户类的密文密码通过密码匹配与用户明文密码进行比对)

我们还有重写一个构造器,在这个构造器中确定认证登录的地址

/*** 自定义过滤器,继承AbstractAuthenticationProcessingFilter类*/
public class MyUserNamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {private static final String USER_NAME="username";private static final String PASSWORD="password";/*** 自定义构造器,传入认证登录的url地址* @param loginUrl 登录的url地址*/public MyUserNamePasswordAuthenticationFilter(String loginUrl) {super(loginUrl);}/*** 尝试去认证的方法,把前端返回的用户名和密码封装到UsernamePasswordAuthenticationToken认证凭对象;* data:{"username":"hhh","password":"123456"}* @param request* @param response* @return* @throws AuthenticationException* @throws IOException* @throws ServletException*/@Overridepublic Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {//判断请求方法必须是post提交,且提交的数据的内容必须是application/json格式的数据if (!request.getMethod().equalsIgnoreCase("POST") || !MediaType.APPLICATION_JSON_VALUE.equalsIgnoreCase(request.getContentType())){throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());}//获取前端传入的json数据,并解析成map集合//获取请求参数//获取reqeust请求对象的发送过来的数据流ServletInputStream in = request.getInputStream();//将数据流中的数据反序列化成MapHashMap<String,String> loginInfo = new ObjectMapper().readValue(in, HashMap.class);String username = loginInfo.get(USER_NAME);username = (username != null) ? username : "";username = username.trim();String password = loginInfo.get(PASSWORD);password = (password != null) ? password : "";//将用户名和密码信息封装到认证票据对象下UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);//调用认证管理器认证指定的票据对象return this.getAuthenticationManager().authenticate(authRequest);}/*** 认证成功后执行的方法* @param request* @param response* @param chain* @param authResult* @throws IOException* @throws ServletException*/@Overrideprotected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {//获取封装后的用户对象
//这一个principal对象里的密码为nullUser principal=(User)authResult.getPrincipal();String username = principal.getUsername();Collection<GrantedAuthority> authorities = principal.getAuthorities();//设置返回数据格式response.setContentType(MediaType.APPLICATION_JSON_VALUE);//设置返回数据编码格式response.setCharacterEncoding("UTF-8");Map<String,String> info=new HashMap<>();info.put("msg","认证成功");info.put("code","1");info.put("data","");//把类对象变成json数据String jsonData = new ObjectMapper().writeValueAsString(info);//将json数据返回response.getWriter().write(jsonData);}/*** 认证失败执行的方法* @param request* @param response* @param failed* @throws IOException* @throws ServletException*/@Overrideprotected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {//设置响应数据为jsonresponse.setContentType(MediaType.APPLICATION_JSON_VALUE);//设置响应数据格式response.setCharacterEncoding("UTF-8");//设置响应的数据Map<String,String>info=new HashMap<>();info.put("msg","认证失败");info.put("code","0");info.put("data","");//将数据封装成json数据String jsonData = new ObjectMapper().writeValueAsString(info);response.getWriter().write(jsonData);}
}

 第二步:把自定义的认证过滤器交给spring管理,并配置认证的路径

在这个自定义的认证过滤器bean中,需要注入一个认证管理器的bean

 /*** 自定义认证过滤器*  隐含:如果认证成功,则在安全上下文中维护认证相关信息*      如果安全上下文中存在认证相关信息,则默认的UserNamePasswordAuthenticationFilter认证过滤器就不会执行*      所以执行顺序,自定义的过滤器在前,默认的过滤器在后* @return* @throws Exception*/@Beanpublic MyUserNamePasswordAuthenticationFilter myUserNamePasswordAuthenticationFilter() throws Exception {//构造认证过滤器对象,并设置认证路径 /myLoginMyUserNamePasswordAuthenticationFilter myUserNamePasswordAuthenticationFilter = new MyUserNamePasswordAuthenticationFilter("/myLogin");//注入一个认证管理器beanmyUserNamePasswordAuthenticationFilter.setAuthenticationManager(authenticationManagerBean());return myUserNamePasswordAuthenticationFilter;}

第三步:把自定义的认证过滤器配置在默认认证过滤器之前

因为只要自定义的认证过滤器认证成功,就会在安全上下文中维护认证成功的相关信息,这样就不会去执行默认的认证过滤器

 @Overrideprotected void configure(HttpSecurity http) throws Exception {http.formLogin()//开启默认form表单登录方式.and().logout()//登出用默认的路径登出 /logout.permitAll()//允许所有的用户访问登录或者登出的路径.and().csrf().disable()//启用CSRF,防止CSRF攻击.authorizeRequests();//授权方法,该方法后有若干子方法进行不同的授权规则处理//对于自定义过滤器,需要创建一个实例,所以用方法返回一个自定义认证过滤器http.addFilterBefore(myUserNamePasswordAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);/* //允许所有账户都可访问(不登录即可访问),同时可指定多个路径.antMatchers("/register").permitAll()//允许所有的用户访问.antMatchers("/hello").hasAuthority("P1") //具有P5权限才可以访问.antMatchers("/say").hasRole("SELECT") //具有ROLE_ADMIN 角色才可以访问,会自动加上ROLE_.antMatchers("/aa","/bb").hasAnyAuthority("P1","ROLE_SELECT")//有任意一个权限都可以访问.antMatchers("/aa","/bb").hasAnyRole("SELECT")//有任意一个权限都可以访问.antMatchers("/aa","/bb").hasIpAddress("192.168.xxx.xxx")//必须是192.168.地址才能访问.antMatchers("/aa","/bb").denyAll()//任何用户都不可以访问.anyRequest().authenticated(); //除了上边配置的请求资源,其它资源都必须授权才能访问*/}

 第四步:测试

访问myLogin接口

 基于JWT实现无状态认证 

​ JWT是无状态的,所以在服务器端无需存储和维护认证信息,这样会大大减轻服务器的压力,所以我们可在自定义的认证过滤器认证成功后通过successfulAuthentication方法向前端颁发token认证字符串;

第一步:编写生成jwt票据工具类

public class JwtTokenUtil {// Token请求头public static final String TOKEN_HEADER = "authorization";// Token前缀public static final String TOKEN_PREFIX = "Bearer ";// 签名主题public static final String SUBJECT = "JRZS";// 过期时间,单位毫秒public static final long EXPIRITION = 1000 * 60 * 60* 24 * 7;// 应用密钥public static final String APPSECRET_KEY = "hhha";// 角色权限声明private static final String ROLE_CLAIMS = "role";/*** 生成Token*/public static String createToken(String username,String role) {Map<String,Object> map = new HashMap<>();map.put(ROLE_CLAIMS, role);String token = Jwts.builder()a.setSubject(username).setClaims(map).claim("username",username).setIssuedAt(new Date()).setExpiration(new Date(System.currentTimeMillis() + EXPIRITION)).signWith(SignatureAlgorithm.HS256, APPSECRET_KEY).compact();return token;}/*** 校验Token*/public static Claims checkJWT(String token) {try {final Claims claims = Jwts.parser().setSigningKey(APPSECRET_KEY).parseClaimsJws(token).getBody();return claims;} catch (Exception e) {e.printStackTrace();return null;}}/*** 从Token中获取用户名*/public static String getUsername(String token){Claims claims = Jwts.parser().setSigningKey(APPSECRET_KEY).parseClaimsJws(token).getBody();return claims.get("username").toString();}/*** 从Token中获取用户角色*/public static String getUserRole(String token){Claims claims = Jwts.parser().setSigningKey(APPSECRET_KEY).parseClaimsJws(token).getBody();return claims.get("role").toString();}/*** 校验Token是否过期*/public static boolean isExpiration(String token){Claims claims = Jwts.parser().setSigningKey(APPSECRET_KEY).parseClaimsJws(token).getBody();return claims.getExpiration().before(new Date());}
}

 第二步:在认证成功后执行的方法里生成jwt令牌并返回给前端

/*** 认证成功后执行的方法* @param request* @param response* @param chain* @param authResult* @throws IOException* @throws ServletException*/@Overrideprotected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {//获取封装后的用户对象//这一个principal对象里的密码为nullUser principal=(User)authResult.getPrincipal();String username = principal.getUsername();Collection<GrantedAuthority> authorities = principal.getAuthorities();//使用工具类生成jwt令牌,authorities.toString()把权限集合变成["P1","ROLE_ADMIN]类型//构建JwtToken 加入权限信息是为了将来访问时,jwt解析获取当前用户对应的权限,做授权的过滤String token = JwtTokenUtil.createToken(username, authorities.toString());//设置返回数据格式response.setContentType(MediaType.APPLICATION_JSON_VALUE);//设置返回数据编码格式response.setCharacterEncoding("UTF-8");Map<String,String> info=new HashMap<>();info.put("msg","认证成功");info.put("code","1");info.put("data",token);//把类对象变成json数据String jsonData = new ObjectMapper().writeValueAsString(info);//将json数据返回response.getWriter().write(jsonData);}

第三步:结果

SpringSecurity基于Jwt实现认证小结 

授权过滤器

 授权流程

自定义授权过滤器流程

第一步:自定义一个类继承 OncePerRequestFilter类

重写doFilterInternal方法,在这个方法中,获取请求头的token票据,并进行校验判断

/*** 定义授权过滤器,本质就是获取一切请求头的token信息,进行校验*/
public class AuthenticationFilter extends OncePerRequestFilter {/*** 过滤中执行的方法* @param request* @param response* @param filterChain 过滤器链* @throws ServletException* @throws IOException*/@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {//1.从请求头中获取token字符串String tokenStr = request.getHeader(JwtTokenUtil.TOKEN_HEADER);//2.合法性判断//2.1 判断票据信息是否为空if(StringUtils.isBlank(tokenStr)){//如果票据为空,则放行,但是此时安全上下文中没有认证成功的票据,后续的过滤器如果得不到票据,后面的认证过滤器会进行拦截filterChain.doFilter(request,response);return;}//2.2检查票据是否合法,解析失败Claims claims = JwtTokenUtil.checkJWT(tokenStr);if(claims==null){//票据不合法,直接让过滤器终止response.setContentType(MediaType.APPLICATION_JSON_VALUE);response.setCharacterEncoding("UTF-8");Map<String,String> info=new HashMap<>();info.put("msg","票据无效,请重新认证");info.put("data","");info.put("code","0");//把数据序列化成json格式String jsonData = new ObjectMapper().writeValueAsString(info);response.getWriter().write(jsonData);return;}//2.3//获取用户名String username = JwtTokenUtil.getUsername(tokenStr);//获取用户权限集合 "["P1","ROLE_ADMIN"]"String roles = JwtTokenUtil.getUserRole(tokenStr);//解析用户权限字符串String stripStr = StringUtils.strip(roles, "[]");List<GrantedAuthority> authorities = AuthorityUtils.commaSeparatedStringToAuthorityList(stripStr);//3.组装数据到UsernamePasswordAuthenticationToken票据对象中UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username,null,authorities);//4.将封装的认证票据存入security安全上下文中,这样后续的过滤器就可以直接从安全上下文中获取用户的相关权限信息//TODO:以线程为维度:当前访问结束,那么线程回收,安全上下文中的票据也会回收,下次访问时需要重新解析SecurityContextHolder.getContext().setAuthentication(token);//5.放行请求,后续的过滤器,比如:认证过滤器如果发现安全上下文中存在token票据对象,就不会进行重新认证filterChain.doFilter(request,response);}
}

 第二步:把这个授权过滤器交给spring管理

让这个授权过滤器的优先级高于自定义的认证过滤器,因为如果之前已经认证过,请求头中就会存在token票据,授权过滤器就会解析token票据,并把用户名和用户权限封装到UsernamePasswordAuthenticationToken类对象中,然后把这个对象存入安全上下文中,这样一来后续的认证过滤器就不会重新认证,直接放行

 /*** 维护一个授权过滤器bean,检查jwt票据是否有效,并做相关处理*/@Beanpublic AuthenticationFilter authenticationFilter(){return new AuthenticationFilter();}@Overrideprotected void configure(HttpSecurity http) throws Exception {http.formLogin()//开启默认form表单登录方式.and().logout()//登出用默认的路径登出 /logout.permitAll()//允许所有的用户访问登录或者登出的路径.and().csrf().disable()//启用CSRF,防止CSRF攻击.authorizeRequests();//授权方法,该方法后有若干子方法进行不同的授权规则处理//对于自定义过滤器,需要创建一个实例,所以用方法返回一个自定义认证过滤器http.addFilterBefore(myUserNamePasswordAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);//配置授权过滤器,它是资源安全的屏障,优先级最高,所以需要在自定义的认证过滤器之前http.addFilterBefore(authenticationFilter(), MyUserNamePasswordAuthenticationFilter.class);
}

 第三步:测试

先通过/myLogin路径进行用户认证获取token票据

一开始会被授权过滤器拦截,但是请求头中票据为空,授权过滤器就会放行,进入到认证过滤器(因为访问路径是/myLogin),然后获取票据信息

 使用该票据访问其他资源,访问成功

授权过滤器解析请求头的票据,把解析到的用户名,用户权限信息存入UsernamePasswordAuthenticationToken类对象,然后再将对象存入安全上下文中,这样后面的过滤器就知道这个用户有哪些权限

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

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

相关文章

信息技术网络安全政策制定

为什么要制定网络安全政策&#xff1f; 通常&#xff0c;公司并不认为需要制定网络安全政策。现有的政策是为了保护公司的资产&#xff0c;而数据也是一项资产。 网络安全政策的真正必要性很简单&#xff1a;网络安全并不像锁门或不偷公司笔那么简单。在许多情况下&#xff0…

前海石公园的停车点探寻

前海石公园是真的很美&#xff0c;很多看海人&#xff0c;很多钓鱼佬&#xff0c;很多抓螃蟹的人&#xff0c;很多挖沙子的人&#xff0c;很多拍照的人&#xff0c;尤其是没有大太阳的时间段或每天傍晚或每个放假的时候人气超高&#xff0c;故前海石公园停车真的很紧张。由于前…

Unreal Engine 5 C++: 编辑器工具编写入门01(中文解释)

目录 准备工作 1.创建插件 2.修改插件设置 快速资产操作&#xff08;quick asset action) 自定义编辑器功能 0.创建编辑器button&#xff0c;测试debug message功能 大致流程 详细步骤 1.ctrlF5 launch editor 2.创建新的cpp class&#xff0c;derived from AssetAction…

物联网助力智慧交通:优势与前景

智慧交通是当今城市发展的必然趋势&#xff0c;而物联网技术在交通运输领域的应用正是为实现智慧交通建设提供了前所未有的机遇和优势。物联网作为连接和控制物理世界的重要技术手段&#xff0c;在交通领域的应用极大地改善了交通系统的效率、安全性和环保性。 首先&#xff0c…

上海建站提升在线曝光率的关键

在当今数字化时代&#xff0c;企业在线曝光率的高低直接影响到其市场份额和品牌认知度。尤其是在上海这样的大都市&#xff0c;竞争尤为激烈。以下是提升在线曝光率的一些关键策略。 一、优化网站建设 首先&#xff0c;网站是企业在线形象的“门面”。一个设计美观、用户友好的…

STM32嵌入式编程学习到提高:【4】UART串口打印

------------------------------------------------------------------------------------------------------------------------- 工程文件&#xff1a;放在百度云盘里&#xff0c;需要的自行下载&#xff01;&#xff01;&#xff01; 链接: https://pan.baidu.com/s/14gRne…

专业网站建设必备

专业网站建设不仅仅是简单的页面搭建&#xff0c;更是一项综合性的工程&#xff0c;需要结合行业特性、用户体验和技术创新&#xff0c;打造一个符合企业需求、独具特色的线上空间。 第一印象至关重要 一个企业网站就如同公司的数字名片&#xff0c;第一印象往往决定了用户是否…

【LeetCode】每日一题 2024_9_26 数组元素和与数字和的绝对差(模拟)

前言 每天和你一起刷 LeetCode 每日一题~ LeetCode 启动&#xff01; 题目&#xff1a;数组元素和与数字和的绝对差 代码与解题思路 func differenceOfSum(nums []int) int {sum1, sum2 : 0, 0for _, v : range nums {sum1 vfor v > 0 {sum2 v%10v / 10}}return sum1-s…

零基础学Servlet

零基础学Servlet 一。介绍&#xff1a; servlet是一种比较古老的编写网站的方式&#xff0c;在2010年之前比较流行&#xff0c;在此之后&#xff0c;有一堆大佬创造了Spring&#xff08;一种框架&#xff09;&#xff0c;Spring是针对Servlet进行进一步封装&#xff0c;从而让…

CANopen开源库canfestival的移植

本文记录将CANopen开源库CANfestival移植到GD32F470单片机的过程。CANopen协议理解请参考博客&#xff1a;CANopen协议的理解-CSDN博客 CANfestival开源库下载链接 CSDN链接&#xff1a; https://download.csdn.net/download/heqiunong/89774627 官网链接&#xff1a;https:/…

黑芝麻A1000-Ubuntu20.04(九)yolov5从训练到板端运行过程详解

宿主机&#xff1a;台式电脑 Ubuntu20.04 开发板&#xff1a;A1000&#xff08;烧录版本SDK v2.3.1.2&#xff09; 模型转换容器&#xff1a;bsnn-tools-container-stk-4.2.0 编译容器&#xff1a;a1000b-sdk-fad-2.3.1.2 yolov5使用工程&#xff1a;黑芝麻根据https://github.…

高性能分布式搜索引擎Elasticsearch详解

♥️作者&#xff1a;小宋1021 &#x1f935;‍♂️个人主页&#xff1a;小宋1021主页 ♥️坚持分析平时学习到的项目以及学习到的软件开发知识&#xff0c;和大家一起努力呀&#xff01;&#xff01;&#xff01; &#x1f388;&#x1f388;加油&#xff01; 加油&#xff01…

[vulnhub] Jarbas-Jenkins

靶机链接 https://www.vulnhub.com/entry/jarbas-1,232/ 主机发现端口扫描 扫描网段存活主机&#xff0c;因为主机是我最后添加的&#xff0c;所以靶机地址是135的 nmap -sP 192.168.75.0/24 // Starting Nmap 7.93 ( https://nmap.org ) at 2024-09-21 14:03 CST Nmap scan…

【Linux】Linux基本命令

目录 文件和目录操作&#xff1a; ls cd pwd cp mv rm mkdir rmdir touch clear history which/whereis 文件查看和编辑&#xff1a; cat less head tail vi 或 vim sz/rz echo 系统信息和管理&#xff1a; su uname hostname df free top ps ki…

2025台球展,2025河南台球及配套设施展览会3月举办

阳春三月&#xff0c;年度招商季&#xff0c;壹肆柒中国国际台球产业博览会助力全国台球企业拓市场&#xff1b; 2025中国&#xff08;郑州&#xff09;国际台球产业博览会&#xff08;壹肆柒台球展&#xff09; The 2025 China (Zhengzhou) International Billiards Industry…

C++:采用模板封装顺序表,栈,队列

1.顺序表&#xff1a; list.hpp #ifndef LIST_HPP #define LIST_HPP #include <iostream>using namespace std;template <class L>class Seqlist { private:L *ptr;L size;L len0;public:void init(L n){//堆区申请空间&#xff08;大小为n&#xff09;this->…

博主回归!数据结构篇启动

目录 1>>闲话 2>>数据结构前言 3>>复杂度的概念 4>>时间复杂度 5>>大O渐进表示法 6>>总结 1>>闲话 家人们好久不见&#xff0c;小编军训终于是结束了&#xff0c;大一事情太多了&#xff0c;这几天没时间健身&#xff0c;没时间…

2024.9.26 作业 +思维导图

一、作业 1、什么是虚函数&#xff1f;什么是纯虚函数 虚函数&#xff1a;函数前加关键字virtual&#xff0c;就定义为虚函数&#xff0c;虚函数能够被子类中相同函数名的函数重写 纯虚函数&#xff1a;把虚函数的函数体去掉然后加0&#xff1b;就能定义出一个纯虚函数。 2、基…

el-table+el-form实现表单校验和解决不垂直居中导致的问题

el-tableel-form实现表单校验 1.实现el-table的表单校验 关键点123 2.解决不垂直居中导致的问题 问题效果图 解决方案 .item-align-center {display: inline-flex; }

数据定义语言CREATE的应用

新书速览|SQL Server 2022从入门到精通&#xff1a;视频教学超值版_sql server 2022 出版社-CSDN博客 《SQL Server 2022从入门到精通&#xff08;视频教学超值版&#xff09;&#xff08;数据库技术丛书&#xff09;》(王英英)【摘要 书评 试读】- 京东图书 (jd.com) SQL Se…