shiro 整合 springmvc 实战及源码详解

序言

前面我们学习了如下内容:

5 分钟入门 shiro 安全框架实战笔记

shiro 整合 spring 实战及源码详解

相信大家对于 shiro 已经有了最基本的认识,这一节我们一起来学习写如何将 shiro 与 springmvc 进行整合。

spring mvc 整合源码

maven 依赖

  • 版本号
<properties><jetty.version>9.4.34.v20201102</jetty.version><shiro.version>1.7.0</shiro.version><spring.version>5.2.8.RELEASE</spring.version><taglibs.standard.version>1.2.5</taglibs.standard.version>
</properties>
  • shiro 相关依赖
<dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-core</artifactId><version>${shiro.version}</version>
</dependency>
<dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring</artifactId><version>${shiro.version}</version>
</dependency>
<dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-web</artifactId><version>${shiro.version}</version>
</dependency>
  • 其他依赖

主要是 servlet、spring、数据库和 tags

<dependency><groupId>javax.annotation</groupId><artifactId>javax.annotation-api</artifactId><version>1.3.2</version>
</dependency>
<dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>3.1.0</version><scope>provided</scope>
</dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>${spring.version}</version>
</dependency>
<dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId><version>${spring.version}</version>
</dependency>
<dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId><version>${spring.version}</version>
</dependency><dependency><groupId>org.hsqldb</groupId><artifactId>hsqldb</artifactId><version>2.5.0</version><scope>runtime</scope>
</dependency><dependency><groupId>org.apache.taglibs</groupId><artifactId>taglibs-standard-spec</artifactId><version>${taglibs.standard.version}</version><scope>runtime</scope>
</dependency>
<dependency><groupId>org.apache.taglibs</groupId><artifactId>taglibs-standard-impl</artifactId><version>${taglibs.standard.version}</version><scope>runtime</scope>
</dependency>
  • jetty

依赖于 jetty 作为容器启动:

<plugin><groupId>org.eclipse.jetty</groupId><artifactId>jetty-maven-plugin</artifactId><version>${jetty.version}</version><configuration><httpConnector><port>8080</port></httpConnector><webApp><contextPath>/</contextPath></webApp></configuration>
</plugin>

配置

  • applicaiton.properties

主要指定了 shiro 相关的配置

# Let Shiro Manage the sessions
shiro.userNativeSessionManager = true# disable URL session rewriting
shiro.sessionManager.sessionIdUrlRewritingEnabled = false
# 登录地址
shiro.loginUrl = /s/login
# 登录成功
shiro.successUrl = /s/index
# 未授权
shiro.unauthorizedUrl = /s/unauthorized

LoginController 登录控制器

我们首先来看一下后端的登录控制器:

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;/*** Spring MVC controller responsible for authenticating the user.** @since 0.1*/
@Component
@RequestMapping("/s/login")
public class LoginController {private static transient final Logger log = LoggerFactory.getLogger(LoginController.class);private static String loginView = "login";@RequestMapping(method = RequestMethod.GET)protected String view() {return loginView;}@RequestMapping(method = RequestMethod.POST)protected String onSubmit(@RequestParam("username") String username,@RequestParam("password") String password,Model model) throws Exception {UsernamePasswordToken token = new UsernamePasswordToken(username, password);try {SecurityUtils.getSubject().login(token);} catch (AuthenticationException e) {log.debug("Error authenticating.", e);model.addAttribute("errorInvalidLogin", "The username or password was not correct.");return loginView;}return "redirect:/s/index";}
}

登录的校验非常简单,直接根据页面的账户密码,然后执行登录校验。

LogoutController 登出控制器

登出直接调用对应的 logout 方法,并且重定向到登录页面。

import org.apache.shiro.SecurityUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.AbstractController;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;/*** Controller responsible for logging out the current user by invoking* {@link org.apache.shiro.subject.Subject#logout()}** @since 0.1*/
@Component
@RequestMapping("/s/logout")
public class LogoutController extends AbstractController {@RequestMapping(method = RequestMethod.GET)protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {SecurityUtils.getSubject().logout();return new ModelAndView("redirect:login");}
}

核心组件

当然,上面的实现看起来非常简单。

数据准备

实际上还有一些用户的账户密码信息准备,是直接通过 BootstrapDataPopulator 类实现的,将账户信息存储到内存数据库 hsqldb 中。

SaltAwareJdbcRealm

针对领域信息的获取实现如下:

import org.apache.shiro.authc.*;
import org.apache.shiro.realm.jdbc.JdbcRealm;
import org.apache.shiro.util.ByteSource;
import org.apache.shiro.util.JdbcUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;/*** Realm that exists to support salted credentials.  The JdbcRealm implementation needs to be updated in a future* Shiro release to handle this.*/
public class SaltAwareJdbcRealm extends JdbcRealm {private static final Logger log = LoggerFactory.getLogger(SaltAwareJdbcRealm.class);@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {UsernamePasswordToken upToken = (UsernamePasswordToken) token;String username = upToken.getUsername();// Null username is invalidif (username == null) {throw new AccountException("Null usernames are not allowed by this realm.");}Connection conn = null;AuthenticationInfo info = null;try {conn = dataSource.getConnection();String password = getPasswordForUser(conn, username);if (password == null) {throw new UnknownAccountException("No account found for user [" + username + "]");}SimpleAuthenticationInfo saInfo = new SimpleAuthenticationInfo(username, password, getName());/*** This (very bad) example uses the username as the salt in this sample app.  DON'T DO THIS IN A REAL APP!** Salts should not be based on anything that a user could enter (attackers can exploit this).  Instead* they should ideally be cryptographically-strong randomly generated numbers.*/saInfo.setCredentialsSalt(ByteSource.Util.bytes(username));info = saInfo;} catch (SQLException e) {final String message = "There was a SQL error while authenticating user [" + username + "]";if (log.isErrorEnabled()) {log.error(message, e);}// Rethrow any SQL errors as an authentication exceptionthrow new AuthenticationException(message, e);} finally {JdbcUtils.closeConnection(conn);}return info;}private String getPasswordForUser(Connection conn, String username) throws SQLException {PreparedStatement ps = null;ResultSet rs = null;String password = null;try {ps = conn.prepareStatement(authenticationQuery);ps.setString(1, username);// Execute queryrs = ps.executeQuery();// Loop over results - although we are only expecting one result, since usernames should be uniqueboolean foundResult = false;while (rs.next()) {// Check to ensure only one row is processedif (foundResult) {throw new AuthenticationException("More than one user row found for user [" + username + "]. Usernames must be unique.");}password = rs.getString(1);foundResult = true;}} finally {JdbcUtils.closeResultSet(rs);JdbcUtils.closeStatement(ps);}return password;}}

这里直接通过默认的 sql

select password from users where username = ?

获取账户信息,然后进行最简单的加密验证。

web.xml 配置

细心的小伙伴也许发现了,这个 mvc 项目中没有 web.xml 文件。

那么,一般需要指定的配置是如何指定的呢?

官方给出的案例有另外一个配置类实现了这个功能。

import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.filter.DelegatingFilterProxy;
import org.springframework.web.servlet.DispatcherServlet;import javax.servlet.DispatcherType;
import javax.servlet.FilterRegistration;
import javax.servlet.ServletContext;
import javax.servlet.ServletRegistration;
import java.util.EnumSet;/*** Initializes Spring Environment without the need for a web.xml*/
public class ServletApplicationInitializer implements WebApplicationInitializer {@Overridepublic void onStartup(ServletContext container) {//now add the annotationsAnnotationConfigWebApplicationContext appContext = getContext();// Manage the lifecycle of the root application contextcontainer.addListener(new ContextLoaderListener(appContext));FilterRegistration.Dynamic shiroFilter = container.addFilter("shiroFilterFactoryBean", DelegatingFilterProxy.class);shiroFilter.setInitParameter("targetFilterLifecycle", "true");shiroFilter.addMappingForUrlPatterns(EnumSet.allOf(DispatcherType.class), false, "/*");ServletRegistration.Dynamic remotingDispatcher = container.addServlet("remoting", new DispatcherServlet(appContext));remotingDispatcher.setLoadOnStartup(1);remotingDispatcher.addMapping("/remoting/*");ServletRegistration.Dynamic dispatcher = container.addServlet("DispatcherServlet", new DispatcherServlet(appContext));dispatcher.setLoadOnStartup(1);dispatcher.addMapping("/");}private AnnotationConfigWebApplicationContext getContext() {AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();context.setConfigLocation(getClass().getPackage().getName());return context;}}

授权方法

当然,不同的用户登录的权限不同,肯定是因为我们定义了不同的权限。

import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.authz.annotation.RequiresRoles;/*** Business manager interface used for sample application.** @since 0.1*/
public interface SampleManager {/*** Method that requires <tt>role1</tt> in order to be invoked.*/@RequiresRoles("role1")void secureMethod1();/*** Method that requires <tt>role2</tt> in order to be invoked.*/@RequiresRoles("role2")void secureMethod2();/*** Method that requires <tt>permission1</tt> in order to be invoked.*/@RequiresPermissions("permission2")void secureMethod3();
}

这里通过 @RequiresRoles@RequiresPermissions 指定了方法访问需要的角色或者权限。

实战效果

为了便于大家学习,上述代码已经全部开源:

https://github.com/houbb/shiro-inaction/tree/master/shiro-inaction-02-springmvc

登录页面

启动程序,浏览器直接访问 http://localhost:8080/,会被重定向到登录页面。

登录

user1 登录

我们使用 user1 登录:

user1 登录

user2 登录

我们使用 user2 登录:

user2 登录

登出

直接点击页面的登出链接,就可以实现登出。

实现原理

思考

现在,老马和大家一起思考一个问题。

我们在 application.properties 文件中指定了对应的登录/登出路径,那么 shiro 是如何映射并且执行的呢?

答案就是 Filter。

针对每一个请求,shiro 会判断请求的 url 是否和我们指定的 url 匹配,并且调用对应的 filter,然后出发对应的方法。

实际上 shiro 中有很多内置的 filter 实现,我们选取其中的几个做下介绍。

登录验证 Filter

匿名

最简单的就是所有的用户都可以访问,实现也最简单:

import org.apache.shiro.web.filter.PathMatchingFilter;import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;* @since 0.9*/
public class AnonymousFilter extends PathMatchingFilter {/*** Always returns <code>true</code> allowing unchecked access to the underlying path or resource.** @return <code>true</code> always, allowing unchecked access to the underlying path or resource.*/@Overrideprotected boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) {// Always return true since we allow access to anyonereturn true;}}

这种适合登录页面之类的,比如可以指定如下

/user/signup/** = anon

form 表单提交

还有比较常用的就是 form 表单提交,springboot 整合的时候甚至可以省略掉我们写的登录校验实现。

/*** <p>If you would prefer to handle the authentication validation and login in your own code, consider using the* {@link PassThruAuthenticationFilter} instead, which allows requests to the* {@link #loginUrl} to pass through to your application's code directly.** @see PassThruAuthenticationFilter* @since 0.9*/
public class FormAuthenticationFilter extends AuthenticatingFilter {//TODO - complete JavaDocpublic static final String DEFAULT_ERROR_KEY_ATTRIBUTE_NAME = "shiroLoginFailure";public static final String DEFAULT_USERNAME_PARAM = "username";public static final String DEFAULT_PASSWORD_PARAM = "password";public static final String DEFAULT_REMEMBER_ME_PARAM = "rememberMe";private static final Logger log = LoggerFactory.getLogger(FormAuthenticationFilter.class);private String usernameParam = DEFAULT_USERNAME_PARAM;private String passwordParam = DEFAULT_PASSWORD_PARAM;private String rememberMeParam = DEFAULT_REMEMBER_ME_PARAM;private String failureKeyAttribute = DEFAULT_ERROR_KEY_ATTRIBUTE_NAME;public FormAuthenticationFilter() {setLoginUrl(DEFAULT_LOGIN_URL);}@Overridepublic void setLoginUrl(String loginUrl) {String previous = getLoginUrl();if (previous != null) {this.appliedPaths.remove(previous);}super.setLoginUrl(loginUrl);if (log.isTraceEnabled()) {log.trace("Adding login url to applied paths.");}this.appliedPaths.put(getLoginUrl(), null);}//...
}

当然可以有很多种方式,主要就是构建出登录的账户密码信息。

这里继承自 AuthenticatingFilter 实现类,会调用对应的登录方法:

protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {AuthenticationToken token = createToken(request, response);if (token == null) {String msg = "createToken method implementation returned null. A valid non-null AuthenticationToken " +"must be created in order to execute a login attempt.";throw new IllegalStateException(msg);}try {Subject subject = getSubject(request, response);subject.login(token);return onLoginSuccess(token, subject, request, response);} catch (AuthenticationException e) {return onLoginFailure(token, e, request, response);}
}

登出验证 Filter

shiro 也为我们实现了内置的登出过滤器。

/*** Simple Filter that, upon receiving a request, will immediately log-out the currently executing* {@link #getSubject(javax.servlet.ServletRequest, javax.servlet.ServletResponse) subject}* and then redirect them to a configured {@link #getRedirectUrl() redirectUrl}.** @since 1.2*/
public class LogoutFilter extends AdviceFilter {//.../*** Acquires the currently executing {@link #getSubject(javax.servlet.ServletRequest, javax.servlet.ServletResponse) subject},* a potentially Subject or request-specific* {@link #getRedirectUrl(javax.servlet.ServletRequest, javax.servlet.ServletResponse, org.apache.shiro.subject.Subject) redirectUrl},* and redirects the end-user to that redirect url.** @param request  the incoming ServletRequest* @param response the outgoing ServletResponse* @return {@code false} always as typically no further interaction should be done after user logout.* @throws Exception if there is any error.*/@Overrideprotected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {// 获取主题信息Subject subject = getSubject(request, response);// 检测是否只支持 POST 方式登出// Check if POST only logout is enabledif (isPostOnlyLogout()) {// check if the current request's method is a POST, if not redirectif (!WebUtils.toHttp(request).getMethod().toUpperCase(Locale.ENGLISH).equals("POST")) {// 返回对应的非 post 登出的响应return onLogoutRequestNotAPost(request, response);}}// 获取重定向的地址String redirectUrl = getRedirectUrl(request, response, subject);//try/catch added for SHIRO-298:try {// 执行登出方法subject.logout();} catch (SessionException ise) {log.debug("Encountered session exception during logout.  This can generally safely be ignored.", ise);}issueRedirect(request, response, redirectUrl);return false;}//...
}

授权验证 Filter

RolesAuthorizationFilter 角色授权过滤器

import java.io.IOException;
import java.util.Set;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.CollectionUtils;/*** Filter that allows access if the current user has the roles specified by the mapped value, or denies access* if the user does not have all of the roles specified.** @since 0.9*/
public class RolesAuthorizationFilter extends AuthorizationFilter {//TODO - complete JavaDoc@SuppressWarnings({"unchecked"})public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws IOException {// 获取当前主题Subject subject = getSubject(request, response);// 获取需要的角色列表String[] rolesArray = (String[]) mappedValue;if (rolesArray == null || rolesArray.length == 0) {//no roles specified, so nothing to check - allow access.return true;}// 判断是否拥有指定的角色Set<String> roles = CollectionUtils.asSet(rolesArray);return subject.hasAllRoles(roles);}}

PermissionsAuthorizationFilter 权限授权过滤器

import java.io.IOException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;import org.apache.shiro.subject.Subject;/*** Filter that allows access if the current user has the permissions specified by the mapped value, or denies access* if the user does not have all of the permissions specified.** @since 0.9*/
public class PermissionsAuthorizationFilter extends AuthorizationFilter {public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws IOException {// 获取主题Subject subject = getSubject(request, response);// 需要的权限String[] perms = (String[]) mappedValue;boolean isPermitted = true;if (perms != null && perms.length > 0) {if (perms.length == 1) {// 如果列表长度为1,进行校验if (!subject.isPermitted(perms[0])) {isPermitted = false;}} else {// 如果需要多个,执行校验if (!subject.isPermittedAll(perms)) {isPermitted = false;}}}return isPermitted;}
}

小结

这一节我们讲解了如何整合 springmvc 与 shiro,可以发现 shiro 内置了非常多的实现,帮助我们简化登录的设计实现。

不过使用过 springboot 的小伙伴都知道,我们的实现可以变得更加简化。

可以阅读 springboot 与 shiro 的整合:

shiro 整合 springboot 实战笔记

希望本文对你有所帮助,如果喜欢,欢迎点赞收藏转发一波。

我是老马,期待与你的下次相遇。

参考资料

10 Minute Tutorial on Apache Shiro

https://shiro.apache.org/reference.html

https://shiro.apache.org/session-management.html

本文由博客一文多发平台 OpenWrite 发布!

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

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

相关文章

水务界的“数字蝶变”:水务公司重构自我,开启智慧供水新时代

历经六十余载的稳健前行&#xff0c;某水务公司已发展成为国有一档企业中的供水行业佼佼者&#xff0c;不仅主营业务突出&#xff0c;更拥有完善的产业链条。然而&#xff0c;面对供水业务24小时连续作业的高要求&#xff0c;以及业务管理需求的日益复杂化&#xff0c;公司意识…

Qt中添加Mysql驱动

qt连接mysql报错&#xff1a;QSqlDatabase: QMYSQL driver not loaded QSqlDatabase: available drivers: QSQLITE QODBC QODBC3 QPSQL QPSQL7 - №点缀 - 博客园 (cnblogs.com)

【Django开发】0到1开发美多shop项目:Celery短信和用户注册。全md文档笔记(附代码,已分享)

本系列文章md笔记&#xff08;已分享&#xff09;主要讨论django商城项目开发相关知识。本项目利用Django框架开发一套前后端不分离的商城项目&#xff08;4.0版本&#xff09;含代码和文档。功能包括前后端不分离&#xff0c;方便SEO。采用Django Jinja2模板引擎 Vue.js实现…

Python网络爬虫实战:从入门到进阶

Python网络爬虫是一种自动化程序&#xff0c;用于从互联网上抓取、解析和提取数据。这种技术广泛应用于数据分析、机器学习、搜索引擎优化等领域。下面是一个Python网络爬虫的入门到进阶的实战指南&#xff1a; 入门篇 环境准备 安装Python和pip安装必要的库&#xff1a;reque…

突破编程_C++_高级教程(内存管理(1))

1 内存管理基础 C 的内存管理主要涉及如何分配、使用和释放计算机内存&#xff0c;以确保程序的正确运行和性能优化。其重要任务是避免内存泄漏和野指针等问题。 C 的内存管理是一个涉及多个方面和复杂概念的重要主题。正确地管理内存对于编写高效、稳定和安全的程序至关重要。…

网页403错误(Spring Security报异常 Encoded password does not look like BCrypt)

这个错误通常表现为"403 Forbidden"或"HTTP Status 403"&#xff0c;它指的是访问资源被服务器理解但拒绝授权。换句话说&#xff0c;服务器可以理解你请求看到的页面&#xff0c;但它拒绝给你权限。 也就是说很可能测试给定的参数有问题&#xff0c;后端…

学习Redis基础篇

1.初识Redis 1.认识NoSQL 2.认识Redis 3.连接redis命令 4.数据结构的介绍 5.通用命令 2.数据类型 1.String类型 常见命令&#xff1a;例子&#xff1a;set key value

Vue3实现页面顶部进度条

Vue3页面增加进度条 新建进度条组件新建bar.ts导航守卫中使用 Vue3项目使用导航守卫给页面增加进度条 新建进度条组件 loadingBar.vue <template><div class"wraps"><div ref"bar" class"bar"></div></div> <…

VSCODE上使用python_Django_创建最小项目

接上篇 https://blog.csdn.net/weixin_44741835/article/details/136135996?csdn_share_tail%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22136135996%22%2C%22source%22%3A%22weixin_44741835%22%7D VSCODE官网&#xff1a; Editing Python …

精酿啤酒:麦芽与啤酒花搭配的奥秘

麦芽和啤酒花是啤酒酿造过程中不可或缺的原料&#xff0c;它们的风味和特点对啤酒的口感和品质产生着深远的影响。Fendi Club啤酒在麦芽与啤酒花的搭配方面有着与众不同的技巧和见解&#xff0c;让啤酒的口感更加丰富和迷人。 首先&#xff0c;麦芽的选择是啤酒酿造的关键之一。…

mysql之CRUD常见函数union查询

select select * from c insert 字段设置自增后&#xff0c;当我们指定增加一条数据后&#xff0c;往后增加的数据都会在该条数据后进行递增&#xff0c;但是可以认为的指定增加某条id不存在的数据 insert into c values(7,‘政治’) insert into c(c2) values(‘历史1’),(…

springboot解决@Autowired注入service时出现循环依赖问题

在Spring Boot开发过程中&#xff0c;Autowired注入Service时出现循环依赖是一个常见问题。循环依赖指的是两个或多个Bean相互依赖&#xff0c;形成闭环&#xff0c;导致Spring容器无法正常初始化这些Bean。这里提供几种解决Spring Boot中Autowired注入Service时循环依赖问题的…

掌握网络未来:深入解析RSVP协议及其在确保服务质量中的关键作用

第一部分&#xff1a;RSVP简介 资源预留协议&#xff08;RSVP&#xff09;是一种网络协议&#xff0c;用于在网络中的各个节点之间预留资源&#xff0c;以支持数据流的服务质量&#xff08;QoS&#xff09;要求。RSVP特别适用于需要固定带宽和处理延迟的应用&#xff0c;如视频…

基于AT89C51单片机与DS18B20的温度测量系统

摘 要:DALLAS 公司的单总线数字温度传感器DSl8B20 以其线路简单、硬件开销少、成本低廉等一系列优点,有着无可比拟的应用前景。文章首先介绍了DSl8B20 的特性及工作原理。接着提出了一种基于AT89C51 单片机与DS18B20 的温度测量报警系统,分析了系统的硬件结构及软件设计。其…

Vue3 父子组件传参

父子组件传值 1. 父组件给子组件传值 —— v-bind 传递字符串类型&#xff0c;不需要加 v-bind&#xff08;:&#xff09; 传递非字符串类型&#xff0c;需要加 v-bind&#xff08;:&#xff09; 2. 子组件接收父组件的传值 —— defineProps 3.子组件给父组件传参&#xf…

【目标检测新SOTA!v7 v4作者新作!】YOLO v9 思路复现 + 全流程优化

YOLO v9 思路复现 全流程优化 提出背景&#xff1a;深层网络的 信息丢失、梯度流偏差YOLO v9 设计逻辑可编程梯度信息&#xff08;PGI&#xff09;&#xff1a;使用PGI改善训练过程广义高效层聚合网络&#xff08;GELAN&#xff09;&#xff1a;使用GELAN改进架构 对比其他解法…

代码随想录算法训练营总结 | 慢慢总结,想起啥就先写上

二叉树总结 二叉树的结构 stauct TreeNode {int val&#xff1b;TreeNode* left;TreeNode* right; }二叉树的递归函数分析 二叉树的递归函数当做只有一个根节点&#xff0c;一个左子树&#xff0c;一个右节点的数去看&#xff0c;这看着是个废话&#xff0c; 其实很重要 回溯…

学习数据节构和算法的第13天

单链表 ​ 单链表是一种常见的数据结构&#xff0c;由一个个节点组成。 每个节点包含两个部分&#xff1a;数据部分和指针部分。 **数据部分&#xff1a;**存储节点中的具体数据。可以是任何类型的数据&#xff0c;如整数、浮点数、字符串等。 **指针部分&#xff1a;**指向…

FFMPEG 推流至 NGINX-RTMP 服务

NGINX 是一个非常出色的 HTTP 服务器&#xff0c;FFMPEG 是非常好的音视频框架。通过 NGINX 的 nginx-rtmp-module 模块结合在一起&#xff0c;实现一个流媒体服务器&#xff0c;它支持 RTMP 和 HLS&#xff08;Live Http Stream&#xff09;。 一、FFMPEG 文件推流&#xff1a…

精通Django模板(模板语法、继承、融合与Jinja2语法的应用指南)

模板&#xff1a; 基础知识&#xff1a; ​ 在Django框架中&#xff0c;模板是可以帮助开发者快速⽣成呈现给⽤户⻚⾯的⼯具模板的设计⽅式实现了我们MVT中VT的解耦(M: Model, V:View, T:Template)&#xff0c;VT有着N:M的关系&#xff0c;⼀个V可以调⽤任意T&#xff0c;⼀个…