spring boot(学习笔记第十三课)
spring boot(学习笔记第十三课)
- 传统后端开发模式和前后端分离模式的不同,Spring Security的logout,invalidateHttpSession不好用,bug?
学习内容:
- 传统后端开发模式 vs 前后端分离模式
- Spring Security的logout功能
- invalidateHttpSession不好用,bug?原来还是功力不够!
1. 传统后端开发模式 vs 前后端分离模式
- 传统后端开发模式
上面主要练习传统后端开发模式
,在这种模式下,页面的渲染都是请求后端,在后端完成页面的渲染。认证的页面都是通过https://localhost:8080/loginPage
进行用户名和密码的form
填写,之后重定向到需要认证的资源的页面。
正如[spring boot(学习笔记第十二课)](https://blog.csdn.net/s
ealaugh1980/article/details/140224760)的练习的那样,在传统后端开发模式
,需要配置各种页面..formLogin(form -> form.loginPage("/loginPage").loginProcessingUrl("/doLogin")//这里的url不用使用controller进行相应,spring security自动处理.usernameParameter("uname")//页面上form的用户名.passwordParameter("passwd").defaultSuccessUrl("/index")//默认的认证之后的页面.failureForwardUrl("/loginPasswordError"))//默认的密码失败之后的页面 .exceptionHandling(exceptionHandling ->exceptionHandling.accessDeniedHandler(new CustomizeAccessDeniedHandler()))
- 前后端分离开发模式
现在web application
的已经过渡到了前后端分离开发模式
,而spring boot security
也兼容这种模式。
接下来通过使用postman
,模拟下前后端分离模式的spring security
开发和使用场景。- 指定认证成功和失败的
handler
注意,这里一定要去掉.loginPage("/loginPage")
.formLogin(form -> form.loginProcessingUrl("/loginProcess")//这里对于前后端分离,提供的非页面访问url.usernameParameter("uname").passwordParameter("passwd").successHandler(new SuccessHandler()).failureHandler(new FailureHandler()))
- 定义认证成功和失败的
handler
//success handlerprivate static class SuccessHandler implements AuthenticationSuccessHandler {@Overridepublic void onAuthenticationSuccess(HttpServletRequest httpServletRequest,HttpServletResponse httpServletResponse,Authentication authentication) throws IOException {Object principal = authentication.getPrincipal();httpServletResponse.setContentType("application/json;charset=utf-8");PrintWriter printWriter = httpServletResponse.getWriter();httpServletResponse.setStatus(200);Map<String, Object> map = new HashMap<>();map.put("status", 200);map.put("msg", principal);ObjectMapper om = new ObjectMapper();printWriter.write(om.writeValueAsString(map));printWriter.flush();printWriter.close();}}//failure handlerprivate static class FailureHandler implements AuthenticationFailureHandler {@Overridepublic void onAuthenticationFailure(HttpServletRequest httpServletRequest,HttpServletResponse httpServletResponse,AuthenticationException authenticationException) throws IOException {httpServletResponse.setContentType("application/json;charset=utf-8");PrintWriter printWriter = httpServletResponse.getWriter();httpServletResponse.setStatus(401);Map<String, Object> map = new HashMap<>();map.put("status", 401);if (authenticationException instanceof LockedException) {map.put("msg", "账户被锁定,登陆失败");} else if (authenticationException instanceof BadCredentialsException) {map.put("msg", "账户输入错误,登陆失败");} else {map.put("msg", authenticationException.toString());}ObjectMapper om = new ObjectMapper();printWriter.write(om.writeValueAsString(map));printWriter.flush();printWriter.close();}
- 一定要将
/loginProcess
的permitAll
打开。注意,这里的习惯是将认证相关的url
都定义成login
开头的,并且一起进行/login*
的permitAll
设定@BeanSecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {httpSecurity.authorizeHttpRequests(auth ->auth.requestMatchers("/login*").permitAll()
- 使用
postman
进行认证测试。pattern-1
正确的密码和用户名
这里使用http://localhost:8080/loginProcess?uname=finlay_user&passwd=123456
进行访问。注意,一定要是用post
,不能使用get
。
这里看到SuccessHandler
pattern-2
错误的密码和用户名
- 认证成功,但是访问资源权限不够,需要设置
exceptionHandling
。- 设置
exceptionHandling.accessDeniedHandler
.exceptionHandling(exceptionHandling ->exceptionHandling.accessDeniedHandler(new CustomizeAccessDeniedHandler()))
- 定义
exceptionHandler
注意,在上一课传统后端开发模式
的时候,定义的是redirect
到画面,但是前后端分离模式
,定义JSON
返回值- 传统后端开发模式
// 传统后端开发模式 private static class CustomizeAccessDeniedHandler implements AccessDeniedHandler {@Overridepublic void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {response.sendRedirect("/loginNoPermissionError");} }
- 传统前后端分离开发模式(
JSON
返回)
// 传统前后端开发模式 private static class CustomizeAccessDeniedHandler implements AccessDeniedHandler {@Overridepublic void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {response.sendRedirect("/loginNoPermissionError");} }
- 访问
/loginProcess
,使用finlay_user(ROLE==user)进行登录
- 访问
/db/hello
,这里需要ROLE==DBA)
进行登录,但是目前的httpSession
不满足条件。
- 设置
- 指定认证成功和失败的
2. Spring Security的logout功能
这里httpSession
的如果需要logout
,这里练习如何进行logout
动作。
传统后端开发模式
如何开发logout
注意,这里传统后端开发模式
需要将successHandler
,failureHandler
和logoutSuccessHandler
都注释掉,否则,这个的对应的url
设置都会无效.formLogin(form ->form.loginProcessingUrl("/loginProcess")//这里对于前后端分离,提供的非页面访问url.usernameParameter("uname").passwordParameter("passwd").loginPage("/loginPage").failureForwardUrl("/loginPasswordError").successForwardUrl("/index")) // .successHandler(new SuccessHandler()) // .failureHandler(new FailureHandler())).logout(httpSecurityLogoutConfigurer ->httpSecurityLogoutConfigurer.logoutUrl("/logout").clearAuthentication(true).invalidateHttpSession(true).logoutSuccessUrl("/loginPage")) // .logoutSuccessHandler(new MyLogoutHandler())).exceptionHandling(exceptionHandling ->exceptionHandling.accessDeniedHandler(new CustomizeAccessDeniedHandler())).csrf(csrf -> csrf.disable())//csrf跨域访问无效.sessionManagement(session -> session.maximumSessions(-1).maxSessionsPreventsLogin(true));
- 设置logout处理的
url
.logoutUrl(“/logout”),这里的/logouot
不需要进行对应,spring boot security
会进行响应处理。 - 对logout进行处理
.logout(httpSecurityLogoutConfigurer ->httpSecurityLogoutConfigurer.logoutUrl("/logout").clearAuthentication(true).invalidateHttpSession(true).logoutSuccessUrl("/loginPage"))
clearAuthentication
是Spring Security
中的一个方法,用于清除当前用户的认证信息,即使当前用户注销登录。在SecurityContextHolder
中保存的SecurityContext
对象将被清除,这意味着在下一次调用SecurityContextHolder.getContext()
时,将不再有认证信息。.invalidateHttpSession(true)
是将httpSession
删除,彻底进行logout
。.logoutSuccessUrl("/loginPage"))
调用将重定向到行的页面/logoutPage
,这里是使用登录的页面。注意,这里如果调用.logoutSuccessHandler(new MyLogoutHandler())
进行设定的话,就是使用前后端分离开发模式
,logoutSuccessUrl("/loginPage")
即便设置也会无效。
- 设置logout处理页面(
controller
) 在页面上表示登录用户的用户名@GetMapping("/logoutPage")public String logoutPage(Model model) {String userName = "anonymous";Authentication authentication = SecurityContextHolder.getContext().getAuthentication();if (authentication != null && authentication.isAuthenticated()) {if (authentication.getName() != null) {userName = authentication.getName();}}model.addAttribute("login_user",userName);return "logout";}
- 设置logout处理页面(
html
)<!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><title>logout</title> </head> <body> <div th:text="${login_user}"></div> <form th:action="@{/logout}" method="post"><button type="submit" class="btn">Logout</button> </form> </body> </html>
- 使用
logout
功能进行logout
在显示logout
按钮的同时,也显示出了Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
取出来的login_user
名字。 - 点击
logout
按钮,成功后返回.logoutSuccessUrl("/loginPage"))
- 设置logout处理的
前后端分离开发模式
如何开发logout
-
将
.logoutSuccessUrl("/loginPage"))
替换成.logoutSuccessHandler(new MyLogoutHandler()))
.logout(httpSecurityLogoutConfigurer ->httpSecurityLogoutConfigurer.logoutUrl("/logout").clearAuthentication(true).invalidateHttpSession(true) // .logoutSuccessUrl("/loginPage")).logoutSuccessHandler(new MyLogoutHandler()))
-
定义
MyLogoutHandler
将logout
结果包装成JSON
格式,传给前端。private static class MyLogoutHandler implements LogoutSuccessHandler {@Overridepublic void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {HttpSession session = request.getSession(false);if (session != null) {// 使会话失效session.invalidate();}response.setContentType("application/json;charset=utf-8");PrintWriter printWriter = response.getWriter();response.setStatus(200);Map<String, Object> map = new HashMap<>();map.put("status", 200);map.put("msg", "logout OK");ObjectMapper om = new ObjectMapper();printWriter.write(om.writeValueAsString(map));printWriter.flush();printWriter.close();}}
-
如果
logout
完毕了,没有有效httpSession
,那么访问/db/hello
资源的话,怎么让spring security
返回JSON
,让前端框架接收到呢。这里需要AuthenticationEntryPoint
。- 设定
AuthenticationEntryPoint
.logout(httpSecurityLogoutConfigurer ->httpSecurityLogoutConfigurer.logoutUrl("/logout").clearAuthentication(true).invalidateHttpSession(true) // .logoutSuccessUrl("/loginPage")).logoutSuccessHandler(new MyLogoutHandler())) .exceptionHandling(exceptionHandling ->exceptionHandling.accessDeniedHandler(new CustomizeAccessDeniedHandler()).authenticationEntryPoint(new RestAuthenticationEntryPoint()))
- 定义
AuthenticationEntryPoint
private static class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {@Overridepublic void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);response.setContentType("application/json");String body = "{\"error\":\"Not Authenticated\"}";OutputStream out = response.getOutputStream();out.write(body.getBytes());out.flush();}}
- 设定
-
使用
postman
模拟前端进行login
。 -
模拟前端调用
/logout
进行logout
。 -
模拟前端调用
/db/hello
进行没有httpSession
的访问,期待返回authenciationError
的JSON
应答。
-
3. invalidateHttpSession不好用,bug?原来还是功力不够!
sessionManagement
的设定
在之前的设定中,一直设定的是.sessionManagement(session -> session.maximumSessions(1).maxSessionsPreventsLogin(true));
.maximumSessions(-1)
,这个参数的意思是同一个用户同时登录spring boot security
应用的数量,-1
代表是没有限制,任意多个。在真正的系统中,一般会设定为1
,意味着如果这个用户在另一个终端登录另外一个httpSession
,那么当前的httpSession
会被挤掉。
那也意味着某一个用户执行,login->logout->login
是能够在第二个login
能够成功的,因为这里中间的logout
已经invalidateHttpSession(true)
了,但是试试果真如此吗?sessionManagement
的设定maximumSessions(1)
,之后进行postman
测试- 使用
finlay_dba
用户进行认证
这里没有问题,认证OK。
- 访问
http://localhost:8080:logout
用户进行logout
这里的logout
也没有问题,成功。 - 访问
http://localhost:8080/loginProcess
用户进行再次login
期待能够正常再次login
,但是很遗憾,这里返回exception
,Maximum sessions of 1 for this principal exceeded
。
- 使用
- 如何解决问题
- 问题在于尽管如下代码,在
logout
的时候进行了处理,但是和期待不同
spring boot security
不会将httpSession
彻底无效化,调用了之后,spring boot security
还是认为有httpSession
正在登录,并没有过期expired
。.logout(httpSecurityLogoutConfigurer ->httpSecurityLogoutConfigurer.logoutUrl("/logout").clearAuthentication(true).invalidateHttpSession(true)
- 在一个csdn旺枝大师文章中,给出了解决方法。
spring boot security
使用SessionRegistry
对httpSession
进行管理,所以需要这里Autowired
出来SessionRegistry
的java bean
,使用这个java bean
在LogoutSuccessHandler
里面进行session
的expireNow
的调用。- 首先配置
SessionRegistry
注意,这里的@Configuration public class SessionRegistryConfig {@Beanpublic SessionRegistry getSessionRegistry(){return new SessionRegistryImpl();}}
SessionRegistryImpl
是spring boot security
的内部类,直接使用,不需要定义。 - 在
SecurityConfig
里面直接Autowired
@Configuration public class SecurityConfig {@BeanPasswordEncoder passwordEncoder() {return NoOpPasswordEncoder.getInstance();}@Autowiredprivate SessionRegistry sessionRegistry;
- 在
SecurityConfig
里面的MyLogoutHandler增加处理,调用expireNow()
private static class MyLogoutHandler implements LogoutSuccessHandler {private SecurityConfig securityConfig = null;public MyLogoutHandler(SecurityConfig securityConfig) {this.securityConfig = securityConfig;}@Overridepublic void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {HttpSession session = request.getSession(false);if (session != null) {// 使会话失效session.invalidate();}List<Object> o = securityConfig.sessionRegistry.getAllPrincipals();//退出成功后删除当前用户sessionfor (Object principal : o) {if (principal instanceof User) {final User loggedUser = (User) principal;if (authentication.getName().equals(loggedUser.getUsername())) {List<SessionInformation> sessionsInfo = securityConfig.sessionRegistry.getAllSessions(principal, false);if (null != sessionsInfo && sessionsInfo.size() > 0) {for (SessionInformation sessionInformation : sessionsInfo) {sessionInformation.expireNow();}}}}}response.setContentType("application/json;charset=utf-8");PrintWriter printWriter = response.getWriter();response.setStatus(200);Map<String, Object> map = new HashMap<>();map.put("status", 200);map.put("msg", "logout OK");ObjectMapper om = new ObjectMapper();printWriter.write(om.writeValueAsString(map));printWriter.flush();printWriter.close();}}
- 首先配置
- 进行
login->logout->login
的动作验证- 首先
login
- 其次访问
http://localhost:8080/logout
- 最后再次访问
http://localhost:8080/loginProcess
到此为止,完美的动作确认结束!
- 首先
- 问题在于尽管如下代码,在
相关文章:

spring boot(学习笔记第十三课)
spring boot(学习笔记第十三课) 传统后端开发模式和前后端分离模式的不同,Spring Security的logout,invalidateHttpSession不好用,bug? 学习内容: 传统后端开发模式 vs 前后端分离模式Spring Security的logout功能inv…...
聊聊不再兼容安卓的鸿蒙
鸿蒙NExt已经确定不再兼容安卓系统,这意味着鸿蒙系统在更新迭代上将会展现出更加迅猛的速度。不过,这样的变化也给开发者们带来了不小的挑战。如今,鸿蒙的开发主要推荐使用的是ArkTS,而不是我们熟悉的Java SDK。对于大量习惯于使用…...
创建一个矩形,当鼠标进入这个矩形的时候,这个矩形边线变色,且鼠标变成手型
1.概要 创建一个矩形,当鼠标进入这个矩形的时候,这个矩形边线变色,且鼠标变成手型 2.代码 #include <QApplication> #include "customRectWidget.h" /** qt 6.7版, 创建一个矩形,当鼠标进入这个矩形…...

AI自动生成PPT哪个软件好?高效制作PPT优选这4个
7.15初伏的到来,也宣告三伏天的酷热正式拉开序幕~在这个传统的节气里,人们以各种方式避暑纳凉,享受夏日的悠闲时光。 而除了传统的避暑活动,我们还可以用一种新颖的方式记录和分享这份夏日的清凉——那就是通过PPT的方式将这一传…...
LruCache、Glide和SmartRefreshLayout使用总结
(一)Android智能下拉刷新框架-SmartRefreshLayout https://github.com/scwang90/SmartRefreshLayout?tabreadme-ov-file (二)LruCache使用 使用它可以进行图片的内存缓存 public class ImageLoaderUtil {private LruCache<St…...

Redis中数据分片与分片策略
概述 数据分片是一种将数据分割并存储在多个节点上的技术,可以有效提高系统的扩展性和性能。在Redis中,数据分片主要用于解决单个实例存储容量和性能瓶颈的问题。通过将数据分散存储到多个Redis节点中,可以将负载均衡到不同的服务器上&#…...
leetcode_169. 多数元素
leetcode_169. 多数元素 问题描述 给定一个大小为 n 的数组 nums ,返回其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。你可以假设数组是非空的,并且给定的数组总是存在多数元素。 示例 1: 输入:nums …...

STM32 GPIO的工作原理
STM32的GPIO管脚有下面8种可能的配置:(4输入 2 输出 2 复用输出) (1)浮空输入_IN_FLOATING 在上图上,阴影的部分处于不工作状态,尤其是下半部分的输出电路,实际上是与端口处于隔离状态。黄色的高亮部分显示…...

板级调试小助手(2)ZYNQ自定义IP核构建属于自己的DDS外设
一、前言 在上期文章中讲述了小助手的系统结构和原理。在PYNQ的框架开发中,我们一般可以将PL端当做PS端的一个外设,通过读写寄存器的方式来操作外设的功能,就类似于在开发ARM和DSP中操作外设一样,不同时的是,我们可以通…...
vim+cscope+ctags
一、简单安装 1.安装cscope # apt install cscope 2.安装ctags # apt install ctags 3.taglist安装 下载Vim source code browser plugin - Browse /vim-taglist at SourceForge.net,解压和复制文件 # unzip taglist_46.zip# cp doc/taglist.txt /usr/share/…...

Java 8的变革:函数式编程和Lambda表达式探索
文章目录 一、函数接口二、Lambda表达式简介三、Lambda表达式外部参数四、Lambda范例五、Runnable Lambda表达式 一、函数接口 函数接口是一个具有单个抽象方法的接口,接口设计主要是为了支持 Lambda 表达式和方法引用,使得 Java 能更方便地实现函数式编…...
Java集合框架的内部揭秘:List、Set与Map的深潜之旅
Java集合框架是一套强大的工具,为开发者提供了灵活的数据管理方式。本文将深入剖析List、Set和Map的内部机制,通过详细的示例和扩展讨论,带你领略这些数据容器的真谛。 一、List:有序序列的深度剖析 List接口是一个可以包含重复…...

爬虫(二)——爬虫的伪装
前言 本文是爬虫系列的第二篇文章,主要讲解关于爬虫的简单伪装,以及如何爬取B站的视频。建议先看完上一篇文章,再来看这一篇文章。要注意的是,本文介绍的方法只能爬取免费视频,会员视频是无法爬取的哦。 爬虫的伪装 …...

空安全编程的典范:Java 8中的安全应用指南
文章目录 一、Base64 编码解码1.1 基本的编码和解码1.2 URL 和文件名安全的编码解码器1.3 MIME Base64编码和解码 二、Optional类三、Nashorn JavaScript 一、Base64 编码解码 1.1 基本的编码和解码 Base64 编码: 使用 Base64.getEncoder().encodeToString(origin…...
Docker Machine 深入解析
Docker Machine 深入解析 引言 Docker Machine 是 Docker 生态系统中的一个重要工具,它简化了 Docker 容器环境的配置和管理过程。本文将深入探讨 Docker Machine 的概念、功能、使用场景以及如何在实际环境中高效利用它。 什么是 Docker Machine? Docker Machine 是一个…...

20.x86游戏实战-远线程注入的实现
免责声明:内容仅供学习参考,请合法利用知识,禁止进行违法犯罪活动! 本次游戏没法给 内容参考于:微尘网络安全 工具下载: 链接:https://pan.baidu.com/s/1rEEJnt85npn7N38Ai0_F2Q?pwd6tw3 提…...

06MFC之对话框--重绘元文件
文章目录 实现示例展示需要绘制的窗口/位置控件位置更新下一次示例粗细滑动部分更新重绘元文件(窗口变化内容消失)方法一:使用元文件方法二:兼容设备方法三:使用自定义类存储绘图数据除画笔外功能处理画笔功能处理保存前面画的线及色彩实现示例展示 需要绘制的窗口/位置 …...

鼠标的发明和鼠标“变形记”
注:机翻,未校对。 Who Invented the Computer Mouse? 谁发明了电脑鼠标? It was technology visionary and inventor Douglas Engelbart (January 30, 1925 – July 2, 2013) who revolutionized the way computers worked, turning it fr…...

快捷:通过胶水语言实现工作中测试流程并行、加速
通过胶水语言实现工作中测试流程并行、加速 通过胶水语言实现工作中测试流程并行、加速工作场景(背景)问题抽象(挑战)如何做(行动)获得了什么(结果)后记相关资源 通过胶水语言实现工…...

MySQL 和 PostgreSQL,我到底选择哪个?
MySQL 和 PostgreSQL 是两个广泛使用的关系型数据库管理系统(RDBMS)。它们都具有强大的功能和广泛的社区支持,但在某些方面存在一些差异。本文将详细比较 MySQL 和 PostgreSQL,包括它们的特点、性能、扩展性、安全性以及适用场景等…...

《Qt C++ 与 OpenCV:解锁视频播放程序设计的奥秘》
引言:探索视频播放程序设计之旅 在当今数字化时代,多媒体应用已渗透到我们生活的方方面面,从日常的视频娱乐到专业的视频监控、视频会议系统,视频播放程序作为多媒体应用的核心组成部分,扮演着至关重要的角色。无论是在个人电脑、移动设备还是智能电视等平台上,用户都期望…...

大型活动交通拥堵治理的视觉算法应用
大型活动下智慧交通的视觉分析应用 一、背景与挑战 大型活动(如演唱会、马拉松赛事、高考中考等)期间,城市交通面临瞬时人流车流激增、传统摄像头模糊、交通拥堵识别滞后等问题。以演唱会为例,暖城商圈曾因观众集中离场导致周边…...

理解 MCP 工作流:使用 Ollama 和 LangChain 构建本地 MCP 客户端
🌟 什么是 MCP? 模型控制协议 (MCP) 是一种创新的协议,旨在无缝连接 AI 模型与应用程序。 MCP 是一个开源协议,它标准化了我们的 LLM 应用程序连接所需工具和数据源并与之协作的方式。 可以把它想象成你的 AI 模型 和想要使用它…...

全球首个30米分辨率湿地数据集(2000—2022)
数据简介 今天我们分享的数据是全球30米分辨率湿地数据集,包含8种湿地亚类,该数据以0.5X0.5的瓦片存储,我们整理了所有属于中国的瓦片名称与其对应省份,方便大家研究使用。 该数据集作为全球首个30米分辨率、覆盖2000–2022年时间…...
vue3 定时器-定义全局方法 vue+ts
1.创建ts文件 路径:src/utils/timer.ts 完整代码: import { onUnmounted } from vuetype TimerCallback (...args: any[]) > voidexport function useGlobalTimer() {const timers: Map<number, NodeJS.Timeout> new Map()// 创建定时器con…...
是否存在路径(FIFOBB算法)
题目描述 一个具有 n 个顶点e条边的无向图,该图顶点的编号依次为0到n-1且不存在顶点与自身相连的边。请使用FIFOBB算法编写程序,确定是否存在从顶点 source到顶点 destination的路径。 输入 第一行两个整数,分别表示n 和 e 的值(1…...
laravel8+vue3.0+element-plus搭建方法
创建 laravel8 项目 composer create-project --prefer-dist laravel/laravel laravel8 8.* 安装 laravel/ui composer require laravel/ui 修改 package.json 文件 "devDependencies": {"vue/compiler-sfc": "^3.0.7","axios": …...

面向无人机海岸带生态系统监测的语义分割基准数据集
描述:海岸带生态系统的监测是维护生态平衡和可持续发展的重要任务。语义分割技术在遥感影像中的应用为海岸带生态系统的精准监测提供了有效手段。然而,目前该领域仍面临一个挑战,即缺乏公开的专门面向海岸带生态系统的语义分割基准数据集。受…...

【p2p、分布式,区块链笔记 MESH】Bluetooth蓝牙通信 BLE Mesh协议的拓扑结构 定向转发机制
目录 节点的功能承载层(GATT/Adv)局限性: 拓扑关系定向转发机制定向转发意义 CG 节点的功能 节点的功能由节点支持的特性和功能决定。所有节点都能够发送和接收网格消息。节点还可以选择支持一个或多个附加功能,如 Configuration …...

【Linux】自动化构建-Make/Makefile
前言 上文我们讲到了Linux中的编译器gcc/g 【Linux】编译器gcc/g及其库的详细介绍-CSDN博客 本来我们将一个对于编译来说很重要的工具:make/makfile 1.背景 在一个工程中源文件不计其数,其按类型、功能、模块分别放在若干个目录中,mak…...