安全之剑:深度解析 Apache Shiro 框架原理与使用指南

在现代软件开发中,安全性一直是至关重要的一个方面。随着网络攻击和数据泄露的不断增加,我们迫切需要一种强大而灵活的安全框架来保护我们的应用。Shiro框架就是这样一把利剑,它能够轻松地集成到你的项目中,为你的应用提供可靠的安全性保护。

在这里插入图片描述

Shiro框架概述

Apache Shiro是一个强大且易用的Java安全框架,提供了身份验证、授权、密码学和会话管理等功能。它被广泛用于保护各种类型的应用程序,包括Web应用、RESTful服务、移动应用和大型企业级应用。使用Shiro,你可以将安全性集成到应用程序中而不必担心复杂的实现细节。

Shiro的核心概念

在深入了解Shiro的原理之前,我们先来了解一下Shiro的一些核心概念:

  • Subject(主体):代表当前用户,可以是一个人、设备或者其他与应用交互的实体。Subject封装了与安全性相关的操作,如身份验证和授权。

  • SecurityManager(安全管理器):负责管理所有Subject,是Shiro的核心。它协调各种安全组件的工作,确保安全性的全面性。

  • Realm(域):负责验证Subject的身份,并提供与授权数据交互。可以将Realm看作是安全数据源。

  • Authentication(身份验证):验证Subject的身份是否合法。通常包括用户名密码验证、多因素认证等。

  • Authorization(授权):确定Subject是否有权限执行特定的操作。授权是安全框架的另一个关键方面。

  • Session Management(会话管理):处理用户的会话,确保安全地管理用户的状态信息。

Shiro的优势

Shiro的优势在于其简单性和灵活性。相较于其他安全框架,Shiro采用了非常直观的API和清晰的架构,使得开发者能够更轻松地理解和使用。此外,Shiro的可扩展性也是其强大之处,你可以根据自己的需求轻松地定制和扩展功能。

Shiro的安装与配置

现在,让我们一起来了解如何在项目中引入Shiro,并进行基本的配置。在这里,我以一个基于Spring Boot的Web应用为例进行演示。

步骤1:引入Shiro依赖

首先,在你的项目中引入Shiro的依赖。如果你使用Maven,可以在pom.xml中添加以下依赖:

<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring-boot-starter</artifactId>
    <version>1.8.0</version> <!-- 请替换为最新版本 -->
</dependency>

步骤2:配置Shiro

在Spring Boot项目中,Shiro的配置通常是通过ShiroConfig类来完成的。创建一个ShiroConfig类,并配置相关的Bean:

@Configuration
public class ShiroConfig {

    @Bean
    public DefaultWebSecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 设置Realm
        securityManager.setRealm(myRealm());
        return securityManager;
    }

    @Bean
    public MyRealm myRealm() {
        return new MyRealm();
    }

    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
        shiroFilter.setSecurityManager(securityManager);
        // 配置拦截规则等
        // ...
        return shiroFilter;
    }
}

在这个例子中,我们配置了一个DefaultWebSecurityManager,并设置了自定义的MyRealm作为Realm。同时,还配置了ShiroFilterFactoryBean,用于定义拦截规则。

步骤3:编写Realm

创建一个继承AuthorizingRealm的自定义Realm类,用于处理身份验证和授权逻辑:

public class MyRealm extends AuthorizingRealm {

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        // 处理身份验证逻辑,例如从数据库中查询用户信息
        // ...
    }

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        // 处理授权逻辑,例如从数据库中查询用户权限信息
        // ...
    }
}

doGetAuthenticationInfo方法中,你可以根据传入的AuthenticationToken进行用户身份验证;而在doGetAuthorizationInfo方法中,你可以根据PrincipalCollection获取用户的权限信息。

Shiro的身份验证

Shiro的身份验证是整个安全框架的核心。下面,让我们通过一个简单的示例来演示如何在Shiro中进行用户身份验证。

// 获取当前用户
Subject currentUser = SecurityUtils.getSubject();

// 创建一个身份验证令牌
UsernamePasswordToken token = new UsernamePasswordToken("username", "password");

try {
    // 调用登录方法进行身份验证
    currentUser.login(token);
    // 身份验证成功,执行其他逻辑
} catch (AuthenticationException e) {
    // 身份验证失败,处理异常
}

在这个例子中,我们首先获取当前用户的Subject,然后创建一个UsernamePasswordToken,设置用户名和密码。接着,调用currentUser.login(token)方法进行身份验证,如果身份验证失败,将会抛出AuthenticationException异常,你可以在catch块中处理相应的异常信息。

为了更好地了解Shiro的身份验证过程,让我们详细讨论一下MyRealm中的doGetAuthenticationInfo方法。这个方法是Shiro用来处理身份验证逻辑的地方。

@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
    // 将AuthenticationToken转换为UsernamePasswordToken
    UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
    
    // 从token中获取用户名
    String username = token.getUsername();
    
    // 在实际项目中,这里通常是从数据库中根据用户名查询用户信息
    // 这里为了演示,我们假设存在一个用户
    if (!"username".equals(username)) {
        throw new UnknownAccountException("用户不存在");
    }
    
    // 假设数据库中的密码是经过加密的,这里为了演示使用明文密码
    String password = "password";
    
    // 返回认证信息,包括用户名和密码
    return new SimpleAuthenticationInfo(username, password, getName());
}

在这个简单的身份验证逻辑中,我们通过UsernamePasswordToken获取到用户输入的用户名,然后假设在数据库中查询到了对应的用户信息。如果用户名不存在,抛出UnknownAccountException异常表示用户未知。如果存在用户,将明文密码返回给Shiro框架,Shiro会将用户输入的密码与数据库中的密码进行匹配。

需要注意的是,在实际项目中,密码存储应该是经过安全加密的,而不是明文存储。Shiro提供了一些常见的加密算法,你可以根据项目需求选择适当的算法。

Shiro的授权

Shiro的授权功能使我们能够精确地定义用户对应用程序中资源的访问权限。通过授权,我们可以防止未经授权的用户访问敏感数据或执行危险操作。

授权的基本概念

在Shiro中,授权通常分为两个步骤:角色授权权限授权

  • 角色授权:将用户分配给一个或多个角色,每个角色代表一组相关的权限。用户通过角色间接获得权限。

  • 权限授权:直接将权限赋予用户,允许用户执行具体的操作。权限是对应用程序中资源的访问控制。

示例:角色授权

让我们通过一个简单的例子来演示如何在Shiro中进行角色授权。

首先,在MyRealm中重写doGetAuthorizationInfo方法:

@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
    SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
    
    // 获取当前用户的用户名
    String username = (String) principalCollection.getPrimaryPrincipal();
    
    // 假设在数据库中查询到该用户的角色信息
    Set<String> roles = new HashSet<>();
    roles.add("admin"); // 用户拥有admin角色
    
    // 将角色信息设置到AuthorizationInfo中
    authorizationInfo.setRoles(roles);
    
    return authorizationInfo;
}

在这个例子中,我们假设用户拥有admin角色。在实际项目中,你需要从数据库中查询用户的角色信息。

然后,在应用程序中,你可以通过以下方式检查用户是否拥有特定角色:

// 获取当前用户
Subject currentUser = SecurityUtils.getSubject();

// 检查用户是否拥有admin角色
if (currentUser.hasRole("admin")) {
    // 用户拥有admin角色,执行相应逻辑
} else {
    // 用户没有admin角色,执行其他逻辑
}

示例:权限授权

现在,让我们看一个授权中的权限授权的例子。

MyRealm中继续完善doGetAuthorizationInfo方法:

@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
    SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
    
    // 获取当前用户的用户名
    String username = (String) principalCollection.getPrimaryPrincipal();
    
    // 假设在数据库中查询到该用户的权限信息
    Set<String> permissions = new HashSet<>();
    permissions.add("user:read"); // 用户拥有读取用户信息的权限
    
    // 将权限信息设置到AuthorizationInfo中
    authorizationInfo.setStringPermissions(permissions);
    
    return authorizationInfo;
}

在这个例子中,我们假设用户拥有user:read权限。同样,你需要从数据库中查询用户的权限信息。

在应用程序中,你可以通过以下方式检查用户是否拥有特定权限:

// 获取当前用户
Subject currentUser = SecurityUtils.getSubject();

// 检查用户是否拥有user:read权限
if (currentUser.isPermitted("user:read")) {
    // 用户拥有user:read权限,执行相应逻辑
} else {
    // 用户没有user:read权限,执行其他逻辑
}

Shiro的会话管理

Shiro提供了灵活且强大的会话管理功能,用于管理用户的会话状态。会话是指用户在系统中的交互期间保持的状态,通常用于存储用户的登录信息、权限信息以及其他相关数据。

会话管理的基本概念

在Shiro中,会话管理主要涉及以下几个方面:

  • 会话创建和销毁:Shiro会自动管理会话的创建和销毁,你可以配置会话的超时时间。

  • 会话存储:会话中存储用户的身份信息、权限信息等,以便于在用户请求之间共享数据。

  • 会话监听:可以通过会话监听器来监听会话的创建、销毁、过期等事件,以执行一些自定义的逻辑。

示例:会话管理

让我们通过一个简单的例子来演示如何在Shiro中进行会话管理。首先,我们需要配置Shiro的会话管理器和会话DAO。

ShiroConfig中添加以下配置:

@Configuration
public class ShiroConfig {

    @Bean
    public DefaultWebSecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 设置Realm
        securityManager.setRealm(myRealm());
        // 设置会话管理器
        securityManager.setSessionManager(sessionManager());
        // 设置会话DAO
        securityManager.setSessionDAO(sessionDAO());
        return securityManager;
    }

    @Bean
    public MyRealm myRealm() {
        return new MyRealm();
    }

    @Bean
    public DefaultWebSessionManager sessionManager() {
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        // 设置全局会话超时时间(毫秒)
        sessionManager.setGlobalSessionTimeout(1800000); // 30分钟
        // 是否在会话过期后自动删除
        sessionManager.setDeleteInvalidSessions(true);
        // 是否开启定时调度器检测过期会话
        sessionManager.setSessionValidationSchedulerEnabled(true);
        return sessionManager;
    }

    @Bean
    public EnterpriseCacheSessionDAO sessionDAO() {
        // 使用企业级缓存SessionDAO,可以替换为其他实现
        EnterpriseCacheSessionDAO sessionDAO = new EnterpriseCacheSessionDAO();
        // 设置缓存名称,根据实际情况配置
        sessionDAO.setActiveSessionsCacheName("shiro-activeSessionCache");
        return sessionDAO;
    }

    // ...其他配置
}

在这个配置中,我们配置了一个DefaultWebSessionManager作为会话管理器,设置了全局会话超时时间为30分钟。同时,我们使用了EnterpriseCacheSessionDAO作为会话DAO,用于存储会话数据。你可以根据项目需求选择不同的会话DAO实现。

接下来,在MyRealm中,我们可以通过重写doGetAuthenticationInfo方法将用户的身份信息存储到会话中:

@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
    // 将AuthenticationToken转换为UsernamePasswordToken
    UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;

    // 从token中获取用户名
    String username = token.getUsername();

    // 在实际项目中,这里通常是从数据库中根据用户名查询用户信息
    // 这里为了演示,我们假设存在一个用户
    if (!"username".equals(username)) {
        throw new UnknownAccountException("用户不存在");
    }

    // 假设数据库中的密码是经过加密的,这里为了演示使用明文密码
    String password = "password";

    // 返回认证信息,包括用户名和密码
    SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(username, password, getName());

    // 获取当前Subject的会话,存储用户身份信息
    Session session = SecurityUtils.getSubject().getSession();
    session.setAttribute("currentUsername", username);

    return authenticationInfo;
}

在这个例子中,我们使用SecurityUtils.getSubject().getSession()获取当前Subject的会话对象,然后将用户名存储到会话的currentUsername属性中。这样,在整个用户会话期间,我们都可以通过SecurityUtils.getSubject().getSession().getAttribute("currentUsername")获取到当前用户的用户名。

Shiro的其他特性

除了上述介绍的核心功能之外,Shiro还提供了许多其他有用的特性,例如密码加密、RememberMe功能、单点登录等。在这里,简单介绍一下其中的一些特性。

密码加密

在真实项目中,用户密码通常不会以明文形式存储在数据库中,而是经过加密处理。Shiro提供了方便的密码加密工具,可以轻松地对密码进行加密和验证。

首先,在MyRealm中,修改doGetAuthenticationInfo方法,将明文密码加密后返回:

@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
    // 将AuthenticationToken转换为UsernamePasswordToken
    UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;

    // 从token中获取用户名
    String username = token.getUsername();

    // 在实际项目中,这里通常是从数据库中根据用户名查询用户信息
    // 这里为了演示,我们假设存在一个用户
    if (!"username".equals(username)) {
        throw new UnknownAccountException("用户不存在");
    }

    // 假设数据库中的密码是经过加密的
    String encryptedPassword = encryptPassword("password");

    // 返回认证信息,包括用户名和加密后的密码
    return new SimpleAuthenticationInfo(username, encryptedPassword, getName());
}

private String encryptPassword(String password) {
    // 使用Shiro提供的密码加密工具
    return new Md5Hash(password).toHex();
}

在这个例子中,我们使用Md5Hash对密码进行MD5加密。你可以根据实际项目需求选择其他加密算法。

RememberMe功能

Shiro的RememberMe功能允许用户在关闭浏览器后仍然保持登录状态。通过简单的配置,我们可以启用RememberMe功能。

ShiroConfig中添加RememberMe的配置:

@Bean
public CookieRememberMeManager rememberMeManager() {
    CookieRememberMeManager rememberMeManager = new CookieRememberMeManager();
    SimpleCookie rememberMeCookie = new SimpleCookie("rememberMe");
    rememberMeCookie.setMaxAge(2592000); // 设置Cookie的有效期,单位秒,这里为30天
    rememberMeManager.setCookie(rememberMeCookie);
    return rememberMeManager;
}

@Bean
public DefaultWebSecurityManager securityManager() {
    DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
    // 设置Realm
    securityManager.setRealm(myRealm());
    // 设置会话管理器
    securityManager.setSessionManager(sessionManager());
    // 设置会话DAO
    securityManager.setSessionDAO(sessionDAO());
    // 设置RememberMe管理器
    securityManager.setRememberMeManager(rememberMeManager());
    return securityManager;
}

在这个配置中,我们创建了一个CookieRememberMeManager,并设置了RememberMe的Cookie名称和有效期。然后将其添加到DefaultWebSecurityManager中。

单点登录

Shiro还支持单点登录(SSO),使用户能够在多个关联的应用程序中使用同一套凭据进行登录。Shiro的单点登录功能可以通过集成其他身份验证和授权提供程序来实现,其中包括OAuth、CAS等。在这里,我们简单介绍一下使用OAuth 2.0的单点登录配置。

首先,在ShiroConfig中添加OAuth的配置:

@Bean
public OAuth2Realm oAuth2Realm() {
    return new OAuth2Realm();
}

@Bean
public DefaultWebSecurityManager securityManager() {
    DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
    // 设置Realm
    securityManager.setRealm(oAuth2Realm());
    // 设置会话管理器
    securityManager.setSessionManager(sessionManager());
    // 设置会话DAO
    securityManager.setSessionDAO(sessionDAO());
    // 设置RememberMe管理器
    securityManager.setRememberMeManager(rememberMeManager());
    return securityManager;
}

在这个配置中,我们创建了一个OAuth2Realm并将其设置为主Realm。OAuth2Realm是一个自定义的Realm,用于处理OAuth 2.0的身份验证和授权。

接下来,实现OAuth2Realm

public class OAuth2Realm extends AuthorizingRealm {

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        // 处理OAuth 2.0的授权逻辑
        // ...
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        // 处理OAuth 2.0的身份验证逻辑
        // ...
    }
}

doGetAuthenticationInfodoGetAuthorizationInfo方法中,你需要实现OAuth 2.0的身份验证和授权逻辑,具体实现方式取决于你使用的OAuth提供商。

这只是Shiro单点登录的一个简单示例,实际上,单点登录可能涉及到更复杂的协议和配置,具体实现方式取决于你的项目需求。

结语

Apache Shiro作为一款强大且灵活的Java安全框架,为我们提供了全面的安全性解决方案。通过本文的介绍,你应该对Shiro的基本原理、使用方法以及一些高级功能有了初步的了解。

在实际项目中,根据具体需求和项目规模,你可以选择使用Shiro来保护你的应用。不仅如此,Shiro的社区活跃,文档详尽,你可以方便地获取支持和解决问题。

希望这篇博客对你理解和使用Shiro提供了一些帮助。在你的项目中加入这把保护应用的利剑,让你的应用更加安全可靠!

作者信息

作者 : 繁依Fanyi
CSDN: https://techfanyi.blog.csdn.net
掘金:https://juejin.cn/user/4154386571867191

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

用户增长的底层逻辑:从原理到实践

在互联网行业的洪流中&#xff0c;用户增长被视为企业生命力与竞争力的重要标志。理解并掌握用户增长的底层逻辑&#xff0c;是每一位产品经理、市场营销人员以及创业者不可或缺的基本功。 用户增长的底层逻辑&#xff1a;从原理到实践© 由 ZAKER科技 提供 一、用户增长的…

02.percona Toolkit工具pt-archiver命令实践

1.命令作用 Percona Toolkit有的32个命令&#xff0c;可以分为7大类 工具类别 工具命令 工具作用 备注 开发类 pt-duplicate-key-checker 列出并删除重复的索引和外键 pt-online-schema-change 在线修改表结构 pt-query-advisor 分析查询语句&#xff0c;并给出建议&#x…

深度学习知识点:神经网络

深度学习知识点&#xff1a;神经网络 前言神经网络激活函数的优缺点为什么ReLU常用于神经网络的激活函数&#xff1f;梯度消失和梯度爆炸的解决方案&#xff1f;梯度爆炸引发的问题&#xff1f;如何确定是否出现梯度爆炸&#xff1f;神经网络中有哪些正则化技术&#xff1f;批量…

知识图表示学习中的负抽样研究综述

摘要 知识图表示学习(KGRL)或知识图嵌入(KGE)在知识构建和信息探索的人工智能应用中起着至关重要的作用。这些模型旨在将知识图中的实体和关系编码到低维向量空间中。在KGE模型的训练过程中&#xff0c;使用正样本和负样本是区分的必要条件。然而&#xff0c;直接从现有的知识…

Qt 写一个邮件发送程序

最近在完成一个邮箱代替的告警功能&#xff0c;写了一个邮件发送的demo 以下为代码&#xff1a; #ifndef MAINWINDOW_H #define MAINWINDOW_H#include <QMainWindow> #include<QTcpSocket> namespace Ui { class MainWindow; }class MainWindow : public QMainWin…

C语言字节对齐关键字#pragma pack(n)的使用

0 前言 在进行嵌入式开发的过程中&#xff0c;我们经常会见到对齐操作。这些对齐操作有些是为了便于实现指针操作&#xff0c;有些是为了加速对内存的访问。因此&#xff0c;学习如何使用对齐关键字是对于嵌入式开发是很有必要的。 1 对齐规则 1.0 什么叫做对齐 众所周知&a…

深度学习pytorch——多层感知机反向传播(持续更新)

在讲解多层感知机反向传播之前&#xff0c;先来回顾一下多输出感知机的问题&#xff0c;下图是一个多输出感知机模型&#xff1a; 课时44 反向传播算法-1_哔哩哔哩_bilibili 根据上一次的分析深度学习pytorch——感知机&#xff08;Perceptron&#xff09;&#xff08;持续更新…

Django Ajax

【一】Json 【1】介绍 JSON&#xff08;javascript object otaition&#xff09;是一种轻量级的数据交换格式JSON使用了Javascript的一部分语法来定义其数据格式&#xff0c;但Json是独立于语言的Json采用完全独立于语言的文本格式&#xff0c;使得Json成为理想的数据交互语言…

从这15个简洁B端系统控制台页面,你悟到了什么?

从这15个简洁英文 B 端系统控制台页面中&#xff0c;我可以得出以下一些结论&#xff1a; 系统注重简洁性&#xff1a;这些页面采用了简洁的设计风格&#xff0c;避免了过多的装饰和冗余的信息&#xff0c;使用户界面更加清晰和易于使用。用户导航清晰&#xff1a;这些页面通常…

get_ipython()函数使用

今天发现一个很有意思的函数&#xff0c; 在jupyter notebook中&#xff0c;我也可以用命令实现这些值 get_ipython().system(nvidia-smi)结果如下 get_ipython().system(pwd)

学习大数据之JDBC(使用JAVA语句进行SQL操作)

文章目录 DCL语句创建用户授权用户撤销授权查看权限删除用户修改用户密码修改普通用户密码 JDBCjdbc介绍JDBC准备JDBC开发步骤以及详解JDBC注册驱动JDBC获取连接JDBC实现增删改操作JDBC 工具类的使用 DCL语句 我们现在默认使用的都是root用户&#xff0c;超级管理源&#xff0…

计算机基础系列 —— 汇编语言

Same hardware can run many different programs(Software) 文中提到的所有实现都可以参考&#xff1a;nand2tetris_sol&#xff0c;但是最好还是自己学习课程实现一遍&#xff0c;理解更深刻。 我们在之前的文章里&#xff0c;构建了 Register、RAM 和 ALU&#xff0c;使得我…

智慧物联-能源分析平台

物联能源分析平台是为了满足企业对能源管理和节能减排的需求而开发的一套在线平台。随着能源问题日益凸显&#xff0c;企业对能源的使用和管理面临着越来越大的挑战。因此&#xff0c;开发一个能够帮助企业实时监测、分析和优化能源消耗的平台变得尤为重要。 随着工业化和城市…

apifox创建接口含中文字符报错的两种解决方案

针对apifox的含中文报错解决方法&#xff1a; 方法一&#xff1a;创建相应接口后&#xff0c;在设置中URL自动编码为WHATING。 方法二&#xff1a;直接将浏览器的url复制到apifox中&#xff0c;浏览器会自动解析配置中文转换路径。

带大家做一个,易上手的家常水煮肉片

首先 我们泡一些腐竹 如果不会泡 可以查看我的文章 泡软超时干腐竹方法 然后 拿一块猪瘦肉 切 相对厚一点点的薄片 一包豆芽 洗干净 肉片装在大碗中 倒入 小半勺食用盐 适量胡椒粉 倒入一勺生抽 适量蚝油 一点点老抽 一勺淀粉 抓拌均匀 切一些 姜末 蒜末 一把花椒 七八个干…

常见技术难点及方案

1. 分布式锁 1.1 难点 1.1.1 锁延期 同一时间内不允许多个客户端同时获得锁&#xff1b; 1.1.2 防止死锁 需要确保在任何故障场景下&#xff0c;都不会出现死锁&#xff1b; 1.2.3 可重入 特殊的锁机制&#xff0c;它允许同一个线程多次获取同一个锁而不会被阻塞。 1.2…

Embedding #notebook

Embedding 上一个篇章huggingface tokenizer #notebook我们讲解了tokenizer的使用&#xff0c;这一个篇章我们继续讲解所谓的embedding&#xff0c;这是通向模型的第一个层&#xff0c;它实际上就是一个全连接层&#xff0c;那么从一个text文本’我爱中南大学’&#xff0c;经…

【微服务】以模块化单体架构开发微服务应用

目录 推荐超级课程: Docker快速入门到精通Kubernetes入门到大师通关课AWS云服务快速入门实战我们知道,起初,单体应用有显著的优势:它们更容易开发和部署。从开发人员的角度来看,这种简单性是有益的。一切都是集中的,可以快速更新任何部分的业务逻辑并立即看到结果。这种开…

matlab 去除海图的右上角的刻度

matlab 去除海图的右上角的刻度 matlab 去除海图的右上角的刻度 去除&#xff1a; 图片 未去除&#xff1a; 本期不提供图片的代码&#xff1a;以后收费发布&#xff1b;整理中&#xff1b; 本期思路解决方法来自物理海洋科研群中 只需要把m_grid的代码484-485和569-570行…

Redis消息队列与thinkphp/queue操作

业务场景 场景一 用户完成注册后需要发送欢迎注册的问候邮件、同时后台要发送实时消息给用户对应的业务员有新的客户注册、最后将用户的注册数据通过接口推送到一个营销用的第三方平台。 遇到两个问题&#xff1a; 由于代码是串行方式&#xff0c;流程大致为&#xff1a;开…