Shiro认证 -- (Authentication)

Apache Shiro是一个功能强大的Java安全框架,提供了身份验证(Authentication)、授权(Authorization)、加密(Cryptography)、会话管理(Session Management)、与Web集成、缓存(Caching)等核心安全功能。

Shiro认证的核心概念

  1. Subject:代表了当前“用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是Subject,如网络爬虫、机器人等。它是一个抽象概念,所有Subject都绑定到SecurityManager。
  2. SecurityManager:安全管理器,Shiro的核心组件,所有与安全有关的操作都会与SecurityManager交互。它管理着所有Subject,并负责进行认证和授权、会话及缓存的管理。
  3. Realm:域,Shiro从Realm获取安全数据(如用户、角色、权限)。SecurityManager要验证用户身份,需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作。可以把Realm看成DataSource,即安全数据源。
  4. Authenticator:认证器,负责主体认证,是一个扩展点。如果用户觉得Shiro默认的不好,可以自定义实现。其需要认证策略(Authentication Strategy),即什么情况下算用户认证通过了。

 

Shiro认证的流程:

1. 添加依赖项

        <!--        shiro认证--><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring</artifactId><version>2.0.1</version></dependency><!--  导入shiro标签      --><dependency><groupId>com.github.theborakompanioni</groupId><artifactId>thymeleaf-extras-shiro</artifactId><version>2.1.0</version>

2. 收集用户身份/凭证:如用户名/密码。 配置shiro.ini文件

 3.认证测试

编写测试类

@Testpublic void testShiro(){// 1.创建 Realm(安全数据源)IniRealm realm = new IniRealm("classpath:shiro.ini");// 2.配置DefaultSecurityManager securityManager = new DefaultSecurityManager(realm);// 创建注入的 Realm(安全数据源)securityManager.setRealm(realm);SecurityUtils.setSecurityManager(securityManager);// 3.操作 Subject , 进行认证Subject subject = SecurityUtils.getSubject();// 封装一个令牌UsernamePasswordToken token = new UsernamePasswordToken("admin", "123456");try {subject.login(token); // 登录,即认证}catch (AuthenticationException e){System.out.println("认证异常:");e.printStackTrace();}System.out.println("是否通过认证: " + subject.isAuthenticated());// 认证通过后 进行权限认证System.out.println("是否为管理员角色:" + subject.hasRole("管理员"));  // 是否为某角色System.out.println("是否能操作用户查看功能:" + subject.isPermitted("user:view")); // 判断是否拥有某权subject.checkPermission("user:view");System.out.println("身份信息: " + subject.getPrincipal());/*** 18:43:30.420 [main] INFO org.apache.shiro.session.mgt.AbstractValidatingSessionManager -- Enabling session validation scheduler...* 是否通过认证: true* 是否为管理员角色:true* 是否能操作用户查看功能:true* 身份信息: admin*/}

 可以看到我们的一个运行结果 身份认证成功 

 

那如果我们修改密码呢:

  可以看到如果密码和我们的不一样就会 认证异常 !!

  1.  首先通过指定一个ini配置文件来创建一个IniRealm对象;
  2. 接着实例化一个 DefaultSecurityManager,并注入IniRealm 对象;
  3. 再将 DefaultSecurityManager 绑定到 SecurityUtils,这是一个全局设置,设置一次即可通过 SecurityUtils 得到 Subject;
  4. 然后获取身份验证的 Token,如用户名/密码,此处使用UsernamePasswordToken;调用 subject.login 方法进行登录,其会自动委托给 SecurityManager.login 方法进行登录:
  5. 如果身份验证失败请捕获 AuthenticationException或其子类,常见的如:DisabledAccountException(禁用的帐号)、LockedAccountException(锁定的帐号)、UnknownAccountException(错误的帐号)、ExcessiveAttemptsException(登录失败次数过多)、IncorrectCredentialsException(错误的凭证)、ExpiredCredentialsException(过期的凭证)等,具体请查看其继承关系;对于页面的错误消息展示,最好使用如“用户名/密码错误”而不是“用户名错误”/“密码错误”,防止一些恶意用户非法扫描帐号库;
  6. 如果身份认证成功,后续可以使用subject.isAuthenticated()判断是否认证通过subject.getPrincipal()获得身份信息等。

那么 Shiro认证的流程到低是什么呢?

流程如下:

  1. 首先调用 Subject.login(token)进行登录,其会自动委托给 Security Manager, 调用之前必须通过 SecurityUtils.setSecurityManager()设置Security Manager;
  2. SecurityManager负责真正的身份验证逻辑;它会委托给Authenticator进行身份验证:
  3. Authenticator才是真正的身份验证者,ShiroAPI中核心的身份认证入口点,此处可以自定义插入自己的实现;
  4. Authenticator可能会委托给相应的AuthenticationStrategy进行多Realm 身份验证默认 ModularRealmAuthenticator 会调用 AuthenticationStrategy 进行Realm 身份验证;
  5. Authenticator会把相应的token传入Realm,从Realm获取身份信息,如果没有返验证;回抛出异常表示身份验证失败了。此处可以配置多个ealm,将按照相应的顺序及策略进行访问。

 

Shiro 的使用方法:

ShiroConfig :

package com.ty.springbootshiro.config;import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import com.ty.springbootshiro.entity.Right;
import com.ty.springbootshiro.service.RoleService;
import jakarta.annotation.Resource;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jmx.export.metadata.ManagedOperation;import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;/*** ShiroConfig** @aurhor Administrator  whs* @since 2024/10/8*/
@Configuration
public class ShiroConfig {@Resourceprivate RoleService roleService;/*** 开启Shiro注解* @return*/@Beanpublic DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();advisorAutoProxyCreator.setProxyTargetClass(true);return advisorAutoProxyCreator;}/*** 开启aop注解支持* @return*/@Beanpublic AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);return authorizationAttributeSourceAdvisor;}@Bean(name = "shiroDialect")public ShiroDialect shiroDialect() {  // thymeleaf 页面上使用 shiro 标签return new ShiroDialect();}@Beanpublic MyShiroRealm myShiroRealm() {   // 自定义RealmMyShiroRealm myShiroRealm = new MyShiroRealm();return myShiroRealm;}@Beanpublic SecurityManager securityManager() {  // 安全管理器 SecurityManagerDefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();//注入beansecurityManager.setRealm(myShiroRealm());SecurityUtils.setSecurityManager(securityManager);return securityManager;}@Beanpublic ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) { // 过滤器 权限验证ShiroFilterFactoryBean shiroFilterFactory = new ShiroFilterFactoryBean();// 注入securityManagershiroFilterFactory.setSecurityManager(securityManager);// 权限验证 : 使用 Filter 控制资源(URL)的访问shiroFilterFactory.setLoginUrl("/index");shiroFilterFactory.setSuccessUrl("/main");shiroFilterFactory.setUnauthorizedUrl("/403"); //没有权限跳转到403Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String,String>(); // 必须使用LinkedHashMap 有序集合// 配置可以匿名访问资源的url: 静态资源filterChainDefinitionMap.put("/css/**", "anon");filterChainDefinitionMap.put("/fonts/**", "anon");filterChainDefinitionMap.put("/images/**", "anon");filterChainDefinitionMap.put("/js/**", "anon");filterChainDefinitionMap.put("/localcss/**", "anon");filterChainDefinitionMap.put("/localjs/**", "anon");filterChainDefinitionMap.put("/login/**", "anon");filterChainDefinitionMap.put("/logout/**", "anon");  // 注销过滤器 , 自动注销// 配置需要特定权限才能范文的资源的url// 静态授权: 包括全部需要特定权限才能访问的资源URlfilterChainDefinitionMap.put("/user/list", "perms[用户列表]");filterChainDefinitionMap.put("/user/add", "perms[用户添加]");filterChainDefinitionMap.put("/user/edit", "perms[用户编辑]");filterChainDefinitionMap.put("/user/del", "perms[用户删除]");// 动态授权List<Right> rights = roleService.findAllRights();for (Right right : rights) {if (right.getRightUrl()!=null && right.getRightUrl().trim().equals("")) { // .startsWith("/user/")filterChainDefinitionMap.put(right.getRightUrl(), "perms["+right.getRightUrl()+"]");}}// 配置认证访问 : 其他资源URl必须认证通过才能访问filterChainDefinitionMap.put("/**", "authc");  // 必须放在过滤器链的最后面shiroFilterFactory.setFilterChainDefinitionMap(filterChainDefinitionMap);return shiroFilterFactory;}}

indexContriller:

package com.ty.springbootshiro.controller;import com.alibaba.druid.sql.visitor.functions.Right;
import com.ty.springbootshiro.entity.Role;
import com.ty.springbootshiro.entity.User;
import com.ty.springbootshiro.service.RoleService;
import com.ty.springbootshiro.service.UserService;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpSession;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;import java.util.List;/*** IndexConfig** @aurhor Administrator  whs* @since 2024/9/13*/
@Controller
public class IndexController {@Resourceprivate UserService userService;@Resourceprivate RoleService roleService;/*** 去登录页*/@GetMapping("/login")public String toLogin() {return "login";}@RequestMapping("/main")public String main() {return "main";}@RequestMapping("/403")public String unauthorized() {return "403";}@RequestMapping("/login")public String login(String usrName, String usrPassword, Model model, HttpSession session) {
//        User loginUser = userService.login(usrName, usrPassword);
//        if (loginUser != null) {
//            session.setAttribute("loginUser", loginUser);
//            return "redirect:/main";
//        }else {
//            model.addAttribute("meg","账号或密码错误");
//            return "login";
//        }try {UsernamePasswordToken token = new UsernamePasswordToken(usrName, usrPassword);Subject subject = SecurityUtils.getSubject();subject.login(token); // 认证登录User user = (User) subject.getPrincipal();System.out.println("user ------ > " + user);session.setAttribute("loginUser", user);//获取权限
//            Role role = user.getRole();
//            List<Right> rights = roleService.findRightsByRole(role);
//            role.getRights().addAll(rights);
//            model.addAttribute("rights", rights);session.setAttribute("loginUser", user);return "redirect:/main";}catch (UnknownAccountException | IncorrectCredentialsException e) {model.addAttribute("msg", "用户名或密码错误,登录失败!");return "login";}catch (LockedAccountException e) {model.addAttribute("msg", "用户被禁用,登录失败!");return "login";}catch (AuthenticationException e) {model.addAttribute("msg", "认证异常,登录失败!");return "login";}}@RequestMapping("/logout")public String logout(HttpSession session) {session.removeAttribute("loginUser");SecurityUtils.getSubject().logout(); // shiro 注销return "redirect:/main";  //重定向 保证删除Cookie}
}

 

ShiroConfig:

package com.ty.springbootshiro.config;import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import com.ty.springbootshiro.entity.Right;
import com.ty.springbootshiro.service.RoleService;
import jakarta.annotation.Resource;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jmx.export.metadata.ManagedOperation;import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;/*** ShiroConfig** @aurhor Administrator  whs* @since 2024/10/8*/
@Configuration
public class ShiroConfig {@Resourceprivate RoleService roleService;/*** 开启Shiro注解* @return*/@Beanpublic DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();advisorAutoProxyCreator.setProxyTargetClass(true);return advisorAutoProxyCreator;}/*** 开启aop注解支持* @return*/@Beanpublic AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);return authorizationAttributeSourceAdvisor;}@Bean(name = "shiroDialect")public ShiroDialect shiroDialect() {  // thymeleaf 页面上使用 shiro 标签return new ShiroDialect();}@Beanpublic MyShiroRealm myShiroRealm() {   // 自定义RealmMyShiroRealm myShiroRealm = new MyShiroRealm();return myShiroRealm;}@Beanpublic SecurityManager securityManager() {  // 安全管理器 SecurityManagerDefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();//注入beansecurityManager.setRealm(myShiroRealm());SecurityUtils.setSecurityManager(securityManager);return securityManager;}@Beanpublic ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) { // 过滤器 权限验证ShiroFilterFactoryBean shiroFilterFactory = new ShiroFilterFactoryBean();// 注入securityManagershiroFilterFactory.setSecurityManager(securityManager);// 权限验证 : 使用 Filter 控制资源(URL)的访问shiroFilterFactory.setLoginUrl("/index");shiroFilterFactory.setSuccessUrl("/main");shiroFilterFactory.setUnauthorizedUrl("/403"); //没有权限跳转到403Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String,String>(); // 必须使用LinkedHashMap 有序集合// 配置可以匿名访问资源的url: 静态资源filterChainDefinitionMap.put("/css/**", "anon");filterChainDefinitionMap.put("/fonts/**", "anon");filterChainDefinitionMap.put("/images/**", "anon");filterChainDefinitionMap.put("/js/**", "anon");filterChainDefinitionMap.put("/localcss/**", "anon");filterChainDefinitionMap.put("/localjs/**", "anon");filterChainDefinitionMap.put("/login/**", "anon");filterChainDefinitionMap.put("/logout/**", "anon");  // 注销过滤器 , 自动注销// 配置需要特定权限才能范文的资源的url// 静态授权: 包括全部需要特定权限才能访问的资源URlfilterChainDefinitionMap.put("/user/list", "perms[用户列表]");filterChainDefinitionMap.put("/user/add", "perms[用户添加]");filterChainDefinitionMap.put("/user/edit", "perms[用户编辑]");filterChainDefinitionMap.put("/user/del", "perms[用户删除]");// 动态授权List<Right> rights = roleService.findAllRights();for (Right right : rights) {if (right.getRightUrl()!=null && right.getRightUrl().trim().equals("")) { // .startsWith("/user/")filterChainDefinitionMap.put(right.getRightUrl(), "perms["+right.getRightUrl()+"]");}}// 配置认证访问 : 其他资源URl必须认证通过才能访问filterChainDefinitionMap.put("/**", "authc");  // 必须放在过滤器链的最后面shiroFilterFactory.setFilterChainDefinitionMap(filterChainDefinitionMap);return shiroFilterFactory;}}

 shiro 控制的三种状态:

1.

 

2. 

 

3.

 

 以上便是简单的 shiro 认证 下一章我会讲解什么是shiro 授权

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

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

相关文章

【WEB应用安全测试指南–蓝队安全测试2】--超详细-可直接进行实战!!!亲测-可进行安全及渗透测试

安全基础理论入门知识参考上一篇《WEB应用安全测试指南蓝队安全测试1》 WEB应用安全测试指南2 一、文件 I/O 类1.1、任意文件上传1.2、任意文件下载1.3、文件包含 二、接口安全类2.1、短信炸弹2.2、邮件炸弹2.3、短信内容可控2.4、邮件内容可控 三、逻辑流程类3.1、越权3.2、未…

深度学习论文: EfficientCrackNet: A Lightweight Model for Crack Segmentation

深度学习论文: EfficientCrackNet: A Lightweight Model for Crack Segmentation EfficientCrackNet: A Lightweight Model for Crack Segmentation PDF: https://arxiv.org/pdf/2409.18099v1 PyTorch代码: https://github.com/shanglianlm0525/CvPytorch PyTorch代码: https:/…

红日安全vulnstack (一)

目录 环境搭建 本机双网卡 Kali IP 靶机IP Web GetShell 前期信息收集 Yxcms后台模板 Getshell PHPMyAdmin日志 Getshell into outfile写入一句话 X phpmyadmin 日志写入一句话 后渗透 MSF 生成木马上线 提取用户hash值 **hash**加密方式 MSF权限Shell至CS CS …

光标在单词中间,如何通过快捷键选择当前单词?

工具》选项>环境》键盘 &#xff1a;把应用修改成visual studio 6或者 visual assist就可以了

IO编程--单字符、字符串、格式化、模块化实现文件拷贝以及登录注册

一、完成标准io的单字符、字符串、格式化、模块化实现两个文件的拷贝 代码如下&#xff1a; 1.单字符 #include <myhead.h> int main(int argc, const char *argv[]) {//打开文件FILE* fpfopen("test.txt","r"); FILE* fqfopen("copy_test.txt&…

第九课:Python学习之函数基础

函数基础 目标 函数的快速体验函数的基本使用函数的参数函数的返回值函数的嵌套调用在模块中定义函数 01. 函数的快速体验 1.1 快速体验 所谓函数&#xff0c;就是把 具有独立功能的代码块 组织为一个小模块&#xff0c;在需要的时候 调用函数的使用包含两个步骤&#xff…

基于GRNN广义回归网络和MFCC的语音情绪识别matlab仿真,对比SVM和KNN

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 (完整程序运行后无水印) filePath Test_data\悲伤1.wav 类型&#xff1a;悲伤 识别置信度 Vmax 0.9559 2.算法运行软件版本 matlab2022a 3.部…

Vue2路由

1.路由 1.1.Vue路由基础 Vue属于单页应用&#xff08;SPA&#xff09;&#xff0c;即整个应用程序中只有一个html页面。 在单页应用中&#xff08;SPA&#xff09;&#xff0c;由于只是更改DOM来模拟多页面&#xff0c;所以页面浏览历史记录的功能就丧失了。此时&#xff0c…

nextjs项目中,使用postgres的完整案例

目的 通过此案例&#xff0c;可以简单快速的过一下数据库的操作&#xff0c;熟悉app-router这种模式下&#xff0c;client component和server component的两种组件中基本的接口使用。 技术栈 nextjs14.2.* app-routervercel/postgres0.10.*typescript5 重要事情说三遍1 ap…

uni-app写的微信小程序如何体积太大如何处理

方法一&#xff1a;对主包进行分包处理&#xff0c;将使用url: /pages/components/equipment/equipment跳转页面的全部拆分为分包&#xff0c;如url: /pagesS/components/equipment/equipment 在pages.json中添加 "subPackages": [{ "root"…

antd样式修改

1.Tab添加竖线 .ant-tabs .ant-tabs-tab {&::before {position: absolute;top: 50%;inset-inline-end: 0;width: 1px;height: 24px;background-color: #e1e1e1;transform: translateY(-50%);transition: background-color 0.2s;content: "";}} 像这样&#xff…

基于SSM的药品商城系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏&#xff1a;…

科研绘图系列:R语言柱状图(histogram)

文章目录 介绍加载R包数据画图系统信息介绍 柱状图(Bar Chart),也称为条形图(Bar Graph),是一种常用的统计图表,用于展示不同类别的数据量。它由一系列垂直或水平的条形组成,每个条形的长度或高度代表相应类别的数值大小。 加载R包 library(tidyverse)# 显示中文 li…

增量知识 (Incremental Knowledge, IK)

在语义通信系统中&#xff0c;增量知识&#xff08;IK, Incremental Knowledge&#xff09;是一种增强数据传输效率和可靠性的技术&#xff0c;特别是用于混合自动重传请求&#xff08;HARQ, Hybrid Automatic Repeat reQuest&#xff09;机制时。它的核心思想是在传输失败后&a…

Android 15 推出新安全功能以保护敏感数据

Android 15 带来了增强的安全功能&#xff0c;可保护您的敏感健康、财务和个人数据免遭盗窃和欺诈。 它还为大屏幕设备带来了生产力改进&#xff0c;并对相机、消息和密钥等应用进行了更新。 Android 防盗保护 Google 开发并严格测试了一套全面的功能&#xff0c;以在盗窃之…

Stable Diffusion Web UI 大白话术语解释 (二)

归纳整理&#xff0c;Stable Diffusion Web UI 使用过程中&#xff0c;相关术语 ControlNet ControlNet 说简单点&#xff0c;就是你可以给 AI 一些“规则”&#xff0c;比如让它根据某些线条、结构或者骨架去画图。 这样能让 AI 画出更符合你要求的图片&#xff0c;特别适合画…

将爱传递 将“服务好”延伸

从泰康客户,转身成为泰康人,她直言是因为亲身感受了泰康“服务好”的魅力。 入司已8年的泰康养老浙江分公司HWP何英英,是泰康“服务好”的受益者。她从朋友的理赔中见证了泰康服务好的温度与力量,又被泰康养老的职域模式所吸引选择加入泰康。如今,她全身心投入在服务的第一线,…

PMP–知识卡片--项目生命周期与资源投入

预测型生命周期对变更并不友好&#xff0c;尤其是项目后期&#xff0c;变更代价太大&#xff0c;几乎让人无法接受。正如果所示&#xff0c;累计投入线代表项目累计的成本&#xff0c;也代表变更的代价。变更提出得越晚&#xff0c;代价越大。

实时语音转文字(基于NAudio+Whisper+VOSP+Websocket)

今天花了大半天时间研究一个实时语音转文字的程序&#xff0c;目的还包括能够唤醒服务&#xff0c;并把命令提供给第三方。 由于这方面的材料已经很多&#xff0c;我就只把过程中遇到的和解决方案简单说下。源代码开源在AudioWhisper: 实时语音转文字(基于NAudioWhisperVOSPWe…

1.几何算法-凸包

1.凸包的例子 1.1.简单求解 当着手设计一个计算凸包的算法时&#xff0c;此前所给出的凸包定义对我们没有多少帮助。按照定义&#xff0c;需要计算出“包含 P 的所有凸集的交”&#xff0c;可是这种集合有无限多个。而我们所观察到的“CH是一个凸多边形”这一事实&#xff0c…