FastAPI系列12:使用JWT 登录认证和RBAC 权限控制
使用JWT 登录认证和RBAC 权限控制
- 1、身份认证(Authentication)与JWT
- 身份认证(Authentication)的方式
- JWT(JSON Web Token)的实现原理
- 2、授权(Authorization)与RBAC
- 授权(Authorization)的方式
- RBAC的实现原理
- 3、FastAPI 实现 JWT与RBAC
- 实现思路
- 完整的实现代码
在开发 Web API 应用时, 安全问题是非常核心的考虑因素。在众多安全问题中, 身份认证(Authentication)与 授权(Authorization)是首要的问题,本节我们就在FastAPI中提供一个基于JWT和RBAC的登录认证及权限控制的实现方式。
1、身份认证(Authentication)与JWT
身份认证(Authentication)的方式
在Web应用中,身份认证主要为了确保用户是谁,在目前常见的认证方式包括:
认证方式 | 描述 |
---|---|
Session + Cookie | 服务端维护登录状态(Session),前端用 Cookie 自动带上 Session ID |
JWT(JSON Web Token) | 无状态认证,服务端不存储登录状态,Token 中携带用户信息,前端存储于 localStorage 或 Cookie |
OAuth 2.0 | 用于授权第三方访问用户数据(如 GitHub 登录) |
API Key | 简单方式,客户端发送固定密钥,适合低安全场景 |
SAML/OpenID | 企业级单点登录协议,较复杂 |
JWT(JSON Web Token)的实现原理
在上述Web应用认证方式中,JWT实现较为简单,且对前后端分离的开发模式比较友好,应用较为广泛,其原理可以总结为以下几个核心步骤:
1. Token 结构
JWT 由三部分组成,通过 . 分隔,如header.payload.signature
。
- header:声明签名算法,如 HS256。
- payload:实际数据(如用户ID、权限、过期时间等)。
- signature:用密钥对 header 和 payload 进行签名,防止篡改。
2. 签发(生成)过程
服务器在用户登录成功后:
- 构造 header 与 payload;
- 使用密钥和算法生成 signature;
- 拼接三个部分生成最终 Token;
- 将 Token 返回给前端。
3. 验证过程
当客户端带着 Token 访问受保护接口时:
- 服务器解析 Token,读取并验证 signature;
- 检查 exp 是否过期;
- 若验证通过,提取 payload 中的信息用于身份识别和权限判断。
JWT认证过程时序图
2、授权(Authorization)与RBAC
授权(Authorization)的方式
在 Web 应用中,常见的授权方式主要包括以下几种:
方式 | 描述 |
---|---|
RBAC(基于角色) | 用户被分配角色,每个角色有一组权限,最常用,易管理。 |
ABAC(基于属性) | 根据用户属性、资源属性、环境等做出细粒度判断(如年龄、时间、IP)。 |
PBAC(基于策略) | 使用策略语言(如 OPA、Rego)动态决策,灵活但复杂度较高。 |
ACL(访问控制列表) | 每个资源列出允许访问的用户列表,适合小型系统。 |
MAC(强制访问控制) | 安全级别固定(如军事系统),不允许随意改变访问权限。 |
RBAC的实现原理
RBAC(Role-Based Access Control)的实现原理是通过用户-角色-权限三层关系控制资源访问。
核心概念
在RBAC中,有以下三个核心概念:
- 用户(User)
系统中的实体,如员工、用户账号等。 - 角色(Role)
权限的集合,例如 admin、editor、viewer。 - 权限(Permission)
对资源执行操作的能力,如 read:report、delete:user。
在这个三个概念中,一个用户可被分配多个角色,每个角色拥有多个权限,而权限最终控制着具体资源的访问。
授权流程
- 用户登录,系统识别其绑定角色;
- 请求接口时,系统根据角色检索权限;
- 若请求的资源和操作在权限列表中,允许访问;否则拒绝。
3、FastAPI 实现 JWT与RBAC
有了上面关于JWT与RBAC基本理解,下面我们给出在FastAPI 中实现 JWT与RBAC的思路与具体代码示例。
实现思路
JWT 认证实现
- 创建登录接口,用户提交 username/password。
- 验证成功后,生成 JWT Token(用 PyJWT 或 python-jose 库),把用户信息(如 user_id, roles)编码进 token。
- 返回给客户端:access_token(短有效期,如 15 分钟)、refresh_token(长有效期,如 7 天)。
- access_token 过期后,客户端使用 refresh_token 调用 /refresh,获得新的 access_token。
RBAC 权限控制实现
- 在用户 Token 中加上角色(roles)字段。
- 每个接口定义需要的权限角色。
- 写一个 权限校验的依赖项,比如:
from fastapi import Depends, HTTPException, statusdef role_required(required_roles: list):def checker(user = Depends(get_current_user)):if not any(role in user.roles for role in required_roles):raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Forbidden")return userreturn checker
- 然后在路由上使用:
@app.get("/admin/dashboard", dependencies=[Depends(role_required(["admin"]))])
async def admin_dashboard():return {"message": "Welcome, Admin!"}
JWT 认证与RBAC 权限控制的时序图
完整的实现代码
项目目录结构
fastapi_jwt_rbac/
├── app/
│ ├── __init__.py
│ ├── main.py
│ ├── auth.py
│ ├── deps.py
│ └── models.py
├── requirements.txt
app/models.py
from pydantic import BaseModel
from typing import Listclass User(BaseModel):username: strhashed_password: strroles: List[str]
app/auth.py
from jose import jwt, JWTError
from passlib.context import CryptContext
from datetime import datetime, timedelta
from app.models import UserSECRET_KEY = "your_access_secret"
REFRESH_SECRET_KEY = "your_refresh_secret"
ALGORITHM = "HS256"
ACCESS_EXPIRE_MINUTES = 15
REFRESH_EXPIRE_DAYS = 7pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")fake_user_db = {"alice": {"username": "alice","hashed_password": pwd_context.hash("password123"),"roles": ["admin"]},"bob": {"username": "bob","hashed_password": pwd_context.hash("password456"),"roles": ["user"]}
}def verify_password(plain, hashed): return pwd_context.verify(plain, hashed)def authenticate_user(username, password):user = fake_user_db.get(username)if user and verify_password(password, user["hashed_password"]):return User(**user)def create_access_token(data: dict):to_encode = data.copy()expire = datetime.utcnow() + timedelta(minutes=ACCESS_EXPIRE_MINUTES)to_encode.update({"exp": expire})return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)def create_refresh_token(data: dict):to_encode = data.copy()expire = datetime.utcnow() + timedelta(days=REFRESH_EXPIRE_DAYS)to_encode.update({"exp": expire})return jwt.encode(to_encode, REFRESH_SECRET_KEY, algorithm=ALGORITHM)def decode_token(token: str):return jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])def decode_refresh_token(token: str):return jwt.decode(token, REFRESH_SECRET_KEY, algorithms=[ALGORITHM])
app/deps.py
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
from app.auth import decode_token
from app.models import Useroauth2_scheme = OAuth2PasswordBearer(tokenUrl="/login")def get_current_user(token: str = Depends(oauth2_scheme)) -> User:try:payload = decode_token(token)return User(username=payload["sub"], hashed_password="", roles=payload["roles"])except:raise HTTPException(status_code=401, detail="Invalid token")def role_required(roles: list):def checker(user: User = Depends(get_current_user)):if not any(role in user.roles for role in roles):raise HTTPException(status_code=403, detail="Insufficient role")return userreturn checker
app/main.py
from fastapi import FastAPI, Depends, HTTPException, status, Body
from fastapi.security import OAuth2PasswordRequestForm
from app.auth import authenticate_user, create_access_token, create_refresh_token, decode_refresh_token
from app.deps import get_current_user, role_requiredapp = FastAPI()@app.post("/login")
def login(form_data: OAuth2PasswordRequestForm = Depends()):user = authenticate_user(form_data.username, form_data.password)if not user:raise HTTPException(status_code=401, detail="Invalid credentials")return {"access_token": create_access_token({"sub": user.username, "roles": user.roles}),"refresh_token": create_refresh_token({"sub": user.username, "roles": user.roles}),"token_type": "bearer"}@app.post("/refresh")
def refresh(refresh_token: str = Body(...)):try:payload = decode_refresh_token(refresh_token)return {"access_token": create_access_token({"sub": payload["sub"], "roles": payload["roles"]})}except:raise HTTPException(status_code=401, detail="Invalid refresh token")@app.get("/user")
def user_area(user = Depends(get_current_user)):return {"msg": f"Hello {user.username}"}@app.get("/admin")
def admin_area(user = Depends(role_required(["admin"]))):return {"msg": f"Welcome Admin {user.username}"}
requirements.txt
fastapi
uvicorn
python-jose
passlib[bcrypt]
相关文章:
FastAPI系列12:使用JWT 登录认证和RBAC 权限控制
使用JWT 登录认证和RBAC 权限控制 1、身份认证(Authentication)与JWT身份认证(Authentication)的方式JWT(JSON Web Token)的实现原理 2、授权(Authorization)与RBAC授权(…...
定时任务xxl-job国产化改造,适配磐维数据库(PostgreSQL)
前言 因公司要求系统需要全面国产化改造,其中也涉及到定时任务xxl-job的改造。 使用的xxl-job版本为:2.5.0 一、修改配置 1、修改pom.xml,引入postgresql组件 <dependency><groupId>org.postgresql</groupId><artif…...
2025华东杯ABC题赛题已出速拿
2025华东杯ABC题赛题已出速拿 A: B: C:...
PostgreSQL事务与并发清理
1.并发清理概述 清理过程为指定的表,或数据库中的所有表执行以下任务。 1. 移除死元组 移除每一页中的死元组,并对每一页内的活元组进行碎片整理。 移除指向死元组的索引元组。 2. 冻结旧的事务标识(txid) 如有必要…...
基于DeepSeek与HTML的可视化图表创新研究
一、研究背景 在当今数字化时代,数据呈指数级增长,广泛渗透于社会各个领域。无论是商业运营、科学研究,还是公共管理等方面,海量数据蕴含着丰富的潜在价值,成为驱动决策优化、推动业务发展、促进科学创新的关键要素。数…...
游戏引擎学习第250天:# 清理DEBUG GUID
设置阶段,重新开始清理调试层 今天,我们将继续进行之前未完成的任务,主要是清理调试层的代码,并为其在游戏中使用做好准备。昨天我原本准备清理一些代码,但没能完成,所以今天我们将从那里开始,…...
删除k8s某命名空间,一直卡住了怎么办?
以 kubectl delete ns cert-manager 命令卡住为例,并且命名空间一直处于 Terminating 状态,说明 Kubernetes 无法完成删除操作,通常是因为 Finalizers 阻塞或某些资源无法正常清理。 解决方法 1. 检查命名空间状态 kubectl get ns cert-man…...
聊一聊接口自动化测试断言处理策略
目录 一、断言设计原则 1.1精准性 1.2可维护性 1.3容错性 二、常见断言类型及实现 2.1基础验证 2.2响应体验证 2.3业务逻辑验证 2.4异常场景验证 2.5数据库断言 三、断言策略 3.1 精准断言 vs 模糊断言 3.2关键字段优先 3.3数据动态处理 四、多断言处理 4.1单用…...
C# 实现列式存储数据
C#实现列式存储数据指南 一、列式存储概述 列式存储(Columnar Storage)是一种数据存储方式,它将数据按列而非行组织。与传统的行式存储相比,列式存储在以下场景具有优势: 分析型查询:聚合计算、分组统计等操作效率更高…...
vscode中设置eslint保存时自动格式化未生效
vscode中设置eslint保存时自动格式化未生效 设置一 设置二 上述设置二未勾选导致未生效...
力扣HOT100——207.课程表
你这个学期必须选修 numCourses 门课程,记为 0 到 numCourses - 1 。 在选修某些课程之前需要一些先修课程。 先修课程按数组 prerequisites 给出,其中 prerequisites[i] [ai, bi] ,表示如果要学习课程 ai 则 必须 先学习课程 bi 。 例如…...
开源协议全解析:类型、选择与法律风险规避指南
[TOC] 在当今开源软件主导的技术生态中,开源协议(Open Source License)是决定项目能否被商业使用、二次开发的关键法律文件。据统计,GitHub上超过70%的项目使用某种形式的开源协议,但其中近30%存在协议兼容性问题。本…...
Android学习总结之自定义view设计模式理解
面试题 1:请举例说明自定义 View 中模板方法模式的应用 考点分析 此问题主要考查对模板方法模式的理解,以及该模式在 Android 自定义 View 生命周期方法里的实际运用。 回答内容 模板方法模式定义了一个操作的算法骨架,把一些步骤的实现延…...
Kubernetes Ingress 深度解析
Kubernetes Ingress 深度解析 一、Ingress 基本概念 Ingress 是 Kubernetes 中管理外部访问集群服务的 API 对象,提供 HTTP/HTTPS 路由规则,实现以下功能: 基于域名/路径的路由TLS/SSL 终止负载均衡流量控制 与传统服务的区别 特性Ingre…...
rk3568安全启动功能实践
本文主要讲述笔者在rk3568芯片上开发安全启动功能实践的流程。其中主要参考瑞芯微官方文档《Rockchip_Developer_Guide_Secure_Boot_for_UBoot_Next_Dev_CN.pdf》。文档中描述逻辑不是很清晰而且和当前瑞芯微的sdk中安全启动的流程匹配度不高。本文就不再对瑞芯微官方文档的内容…...
transformer-实现解码器Decoder
Decoder 论文地址 https://arxiv.org/pdf/1706.03762 Decoder结构介绍 Transformer Decoder是Transformer模型的核心生成组件,负责基于编码器输出和已生成内容预测后续token。通过堆叠多层结构相同的解码层(Decoder Layer),每层包…...
iOS RunLoop 深入解析
本文深入探讨 iOS 中 RunLoop 的实现原理、工作机制以及实际应用。通过源码分析和实际案例,帮助读者全面理解 RunLoop 在 iOS 系统中的重要作用。 一、RunLoop 基础概念 1. RunLoop 的定义与作用 RunLoop 是 iOS 系统中用于处理事件和消息的循环机制。它负责管理线…...
软考中级-软件设计师 数据结构(手写笔记)
第一章:基础 基础知识 五大特性 第二章:线性表 第三章:栈和队列 队列 广义表 第四章:树和二叉树 基础知识 树转二叉树和二叉排序树 哈夫曼树 线索二叉树和平衡二叉树 第五章:图 基础知识和邻接矩阵和邻接表 图的遍…...
/var/log/sssd/` 目录解析
/var/log/sssd/ 是 System Security Services Daemon (SSSD) 的专用日志目录,用于记录与身份认证、用户/组信息查询、缓存管理等相关的操作。以下是该目录的详细解析: 1. 目录结构 默认情况下,/var/log/sssd/ 包含以下日志文件: /var/log/sssd/ ├── sssd.log …...
C++负载均衡远程调用学习之Reactor事件触发机制
目录 1.LARV0.2-REACTOR_BUF实现 2.LARV0.2-outpu_buf实现 3.LARV0.2-reactor继承内存管理 4.LARV0.2流程总结 5.LARV0.3-多路IO事件的分析 6.LARV0.3_io_event和event_loop定义 7.LARV0.3_event_loop添加一个事件 8.LARV0.3_event_loop的epoll_wait封装 9.LARV0.3-eve…...
将uni-app前端项目发布到微信小程序体验版
1、修改后端接口调用地址 const REQUEST_CONST {BASE_URL:https://11.22.33.44:9090, } export default REQUEST_CONST 2、登录微信小程序平台,获取AppID 3、配置微信小程序AppID 在项目根目录下找到manifest.json文件,配置微信小程序相关的参数 4、…...
深入理解CSS显示模式与盒子模型
一、CSS显示模式:元素的“性格”决定布局 1. 显示模式基础 CSS显示模式(display属性)决定了元素在页面中的排列方式和尺寸表现。常见的显示模式有三大类型: 2. 块级元素(Block) 特点:独占一…...
突破SQL注入字符转义的实战指南:绕过技巧与防御策略
在渗透测试中,SQL注入始终是Web安全的重点攻击手段。然而,当开发者对用户输入的特殊字符(如单引号、反斜杠)进行转义时,传统的注入方式往往会失效。本文将深入探讨如何绕过字符转义限制,并给出防御建议。 目…...
java网络原理5
一、网络地址转换(NAT) 1. 原理 - NAT 用于解决 IP 地址不够用的问题 ,将 IP 地址分为外网 IP(公网 IP)和内网 IP(私网 IP)。内网 IP 如 10.、172.16 - 172.31.、192.168.* 等,家用…...
一种基于光源评估并加权平均的自动白平衡方法(一)
在之前的博文如何在白平衡标定种构建不同类型的白平衡色温坐标系作为实例说明的白平衡色温坐标系的构建中,利用的如下映射矩阵构建色温坐标系: 按照上述论文的说明,是不能直接把Raw域中的每块的RGB带入公式...
基于Docker Compose的Prometheus监控系统一键部署方案
前言 在当今的云原生时代,系统监控已经成为保障业务稳定运行的重要基石。本文旨在提供一个完整的解决方案,帮助您快速搭建一个功能强大的监控系统。通过Docker Compose实现一键部署,结合Prometheus、Grafana、cAdvisor和node-exporter等优秀开源工具,构建一个完整的监控体…...
服务器丢包率测试保姆级教程:从Ping到网络打流仪实战
测试服务器丢包率是网络性能诊断的重要环节,丢包通常由网络拥塞、硬件故障、配置错误或线路质量差导致。以下是多种测试方法的详细步骤和工具说明: 一、基础工具测试(无需专业设备) 1. 使用 ping 命令 命令示例: bash…...
家庭服务器IPV6搭建无限邮箱系统指南
qq邮箱操作 // 邮箱配置信息 // 注意:使用QQ邮箱需要先开启IMAP服务并获取授权码 // 设置方法:登录QQ邮箱 -> 设置 -> 账户 -> 开启IMAP/SMTP服务 -> 生成授权码 服务器操作 fetchmail 同步QQ邮箱 nginx搭建web显示本地同步过来的邮箱 ssh…...
Ubuntu ZLMediakit的标准配置文件(rtsp->rtmp->hls)
最近在工作中遇到不生成hls资源的问题,后面发现是配置文件有误,特此记录正确的config.ini配置文件,方便查阅。 最终解决方案,通过下面这种格式可以访问到flv视频,具体为什么不太清楚,rtmp格式:rtmp://39.113.48.113:8089/live/1744168516937396175 记录最终解决方案:ht…...
Android 移动开发:ProgressBar(转圈进度条)
目录 Android 移动开发:ProgressBar(转圈进度条)控件实战介绍 📂 文件说明 🧾 activity_main.xml(布局文件,XML) 🧾 MainActivity.java(逻辑代码…...
CSS:选择器-复合选择器
文章目录 1、交集选择器 1、交集选择器 <style>/* 选中类名为rich的元素*/.rich {color: gold;}/* 选中类名为beauty的元素*/.beauty {color: red;}/* 选中类名为beauty的p元素,这种形式(元素配合类选择器)以后用的很多!&am…...
Kafka-可视化工具-Offset Explorer
安装: 下载地址:Offset Explorer 安装好后如图: 1、下载安装完毕,进行新增连接,启动offsetexplorer.exe,在Add Cluster窗口Properties 选项下填写Cluster name 和 kafka Cluster Version Cluster name (集…...
在pycharm中创建Django项目并启动
Django介绍 Django 是一个基于 Python 的开源 Web 应用框架,采用了 MTV(Model - Template - View)软件设计模式 ,由许多功能强大的组件组成,能够帮助开发者快速、高效地创建复杂的数据库驱动的 Web 应用程序。它具有以…...
私有知识库 Coco AI 实战(六):打造 ES Mapping 小助手
开发同学可能经常和字段类型打交道,数据类型本来就不少,新版本可能还有新的数据类型。更重要的是新的字段类型可能会提升某个场景的性能,不知道的话可就亏大发了。所以我们继续打造一个 ES Mapping 小助手。 克隆小助手 我们进入 Coco Serv…...
JavaScript性能优化实战之代码层面性能优化
在前端开发中,JavaScript 的性能直接影响到网站的加载速度、用户体验和交互流畅度。针对代码层面的优化,我们可以从多个方面入手,确保每一行代码都能最大化地发挥效能。接下来,我们将细化并解释每一个优化点。 1️⃣ 避免全局变量污染 全局变量会被整个 JavaScript 代码所…...
基于C++的IOT网关和平台2:github项目ctGateway技术说明书
初级代码游戏的专栏介绍与文章目录-CSDN博客 我的github:codetoys,所有代码都将会位于ctfc库中。已经放入库中我会指出在库中的位置。 这些代码大部分以Linux为目标但部分代码是纯C++的,可以在任何平台上使用。 源码指引:github源码指引_初级代码游戏的博客-CSDN博客 …...
前端基础之《Vue(13)—重要API》
重要的API 一、nextTick() 1、写法 Vue.$nextTick()或者this.$nextTick() 原因: set操作代码是同步的,但是代码背后的行为是异步的。set操作修改声明式变量,触发re-render生成新的虚拟DOM,进一步执行diff运算,找到…...
Python爬虫实战:获取彼岸网高清素材图片
一、引言 在数字化时代,图片素材的需求持续增长。彼岸网提供了丰富的高质量图片资源,其中 4K 风景图片备受用户青睐。借助 Python 爬虫技术,可自动化地从彼岸网获取这些图片,为用户提供便捷的图片素材服务。然而,爬取过程中会遭遇登录验证、反爬机制等问题,需采用相应技…...
拥抱 Kotlin Flow
1. 引言 Kotlin Flow 是 Kotlin 协程生态中处理异步数据流的核心工具,它提供了一种声明式、轻量级且与协程深度集成的响应式编程模型。与传统的 RxJava 相比,Flow 更简洁、更易于维护,尤其在 Android 开发中已成为主流选择。本文将从基础概念…...
winget使用
Get-Command winget winget search qq winget install Tencent.QQ.NT...
C++从入门到实战(十一)详细讲解C/C++语言中内存分布与C与C++内存管理对比
C从入门到实战(十一)详细讲解C/C语言中内存分布与C与C内存管理对比 前言一、C/C语言中内存分布1.内核空间2.栈3.堆4.数据段5.代码段 二、例题带练巩固C/C语言中内存分布的知识题目讲解题目答案 三、C语言动态内存分配(知识回顾)3.…...
flutter 专题 一百零四 Flutter环境搭建
Flutter简介 Flutter 是Google开发的一个移动跨平台(Android 和 iOS)的开发框架,使用的是 Dart 语言。和 React Native 不同的是,Flutter 框架并不是一个严格意义上的原生应用开发框架。Flutter 的目标是用来创建高性能、高稳定性…...
傅里叶与相位偏移
一、简介 大三的《离散数学》。。。。。 傅里叶变换是数学与工程领域的一项革命性工具,其核心思想是将复杂信号分解为简单正弦波的叠加,实现从时域(时间维度)到频域(频率维度)的转换。通过这种变换&#x…...
Godot笔记:入门索引
文章目录 前言游戏引擎软件界面关键概念GDScript导出成品创建非游戏应用后记 前言 最近对游戏引擎这块感兴趣,特别是因为游戏引擎自带的很多工具,作为图形化软件的开发应该也不错。 Godot 是一款这几年比较流行的开源游戏引擎。这里记录下入门学习使用 …...
OpenCV实战教程 第一部分:基础入门
第一部分:基础入门 1. OpenCV简介 什么是OpenCV及其应用领域 OpenCV(Open Source Computer Vision Library)是一个开源的计算机视觉和机器学习软件库,于1999年由Intel公司发起,现在由非营利组织OpenCV.org维护。Ope…...
OpenCV 图像处理核心技术 (第二部分)
欢迎来到 OpenCV 图像处理的第二部分!在第一部分,我们学习了如何加载、显示、保存图像以及访问像素等基础知识。现在,我们将深入探索如何利用 OpenCV 提供的强大工具来修改和分析图像。 图像处理是计算机视觉领域的基石。通过对图像进行各种…...
Git从入门到精通-第二章-工具配置
目录 命令行 安装Git 初次运行Git前的配置 git config基本概念 常用命令 配置用户信息 配置文本编辑器 查看配置 配置别名(简化命令) 高级配置 换行符处理(方便跨平台协作) 忽略文件权限变更(常用于团队协…...
树状结构转换工具类
项目中使用了很多树状结构,为了方便使用开发一个通用的工具类。 使用工具类的时候写一个类基础BaseNode,如果有个性化字段添加到类里面,然后就可以套用工具类。 工具类会将id和pid做关联返回一个树状结构的集合。 使用了hutool的工具包判空…...
C#基础简述
C#基础详解 一、C#语言概述 C#(读作"C Sharp")是微软开发的面向对象的编程语言,运行在.NET平台上。它结合了C的强大功能和Visual Basic的简单性,具有以下特点: 面向对象:支持封装、继…...
AI赋能烟草工艺革命:虫情监测步入智能化时代
在当今竞争激烈且品质至上的烟草行业中,生产流程的每一个细微环节都关乎着企业的生死存亡与品牌的兴衰荣辱。烟草工艺部门与制丝、卷包车间作为生产链条的核心驱动,犹如精密仪器中的关键齿轮,彼此紧密咬合、协同运转,任何一处的小…...