【实战Flask API项目指南】之七 用JWT进行用户认证与授权
实战Flask API项目指南之 用JWT进行用户认证与授权
本系列文章将带你深入探索实战Flask API项目指南,通过跟随小菜的学习之旅,你将逐步掌握 Flask
在实际项目中的应用。让我们一起踏上这个精彩的学习之旅吧!
前言
当小菜踏入Flask
后端开发的世界时,了解 JSON Web Token(JWT) 是非常有益的。JWT 是一种用于认证和授权的解决方案,在Flask
中有广泛的应用。它提供了一种安全、可扩展和灵活的方式来管理用户会话和授权。
在上一篇文章中,小菜成功将 本地版图书管理系统后端API 改写成 持久化数据存储的图书管理系统后端API。但是还存在安全隐患,因为后端并没有对每个请求做验证。换句话说,任何人都可以请求数据而不受限制,这显然是不合理的。因此,在本文中,我们将在前一篇文章的基础上,添加安全验证步骤,以增加后端 API 平台的安全性。
JWT具体的内容参考以下链接:
- 阮一峰 - JSON Web Token 入门教程
- Flask-JWT-Extended 官方文档
注意:不要以明文形式存储密码,建议存储密码的哈希值,这里只为了作展示
注意:不要以明文形式存储密码,建议存储密码的哈希值,这里只为了作展示
注意:不要以明文形式存储密码,建议存储密码的哈希值,这里只为了作展示
实现用户认证与授权
1. 用户认证与授权
现在,让我们深入探讨如何在 Flask
应用程序中使用 Flask-JWT-Extended 进行用户认证和授权。
1.1 用户认证
用户认证是确认用户身份的过程。使用 Flask-JWT-Extended库,轻松实现用户登录和JWT生成:
- 用户提供凭据(通常是用户名和密码)进行登录。
- 服务器验证凭据并生成JWT令牌。
- 令牌返回给客户端,客户端将其存储并在以后的请求中发送。
1.2 用户授权
用户授权是确定用户是否具有执行特定操作或访问特定资源的权限的过程。JWT的载荷通常包含有关用户的角色和权限信息。在每个请求中,服务器可以验证令牌中的角色和权限来确定用户是否被授权执行操作。
2. JWT简介
具体的原理戳这里查看啦:阮一峰 - JSON Web Token 入门教程
2.1 JWT结构
JWT由三部分组成:
- 头部(Header):通常包含令牌的类型(JWT)和使用的加密算法。
- 载荷(Payload):包含有关用户或其他数据的信息。例如,用户ID、角色或其他自定义数据。
- 签名(Signature):由头部、载荷和密钥组合而成的签名,用于验证令牌的完整性和来源可信度。
2.2 生成和验证JWT
- 用户登录时,服务器使用密钥签署JWT,并将其返回给客户端。
- 客户端在以后的请求中发送JWT作为身份验证令牌。
- 服务器验证JWT的签名以确保其完整性,然后使用载荷中的信息进行用户身份验证和授权。
3. Flask-JWT-Extended简介
这里只是比较基础的对 Flask-JWT-Extended 的应用,各位读者朋友们可以通过官网去系统的学习 Flask-JWT-Extended。
Flask-JWT-Extended是一个Python库,用于在 Flask
应用程序中添加JSON Web令牌(JWT)支持。它是一个插件,可以通过安装它来扩展Flask应用程序的功能。
可以通过官方文档做系统的学习。
- Flask-JWT-Extended 官方文档
2.1 安装依赖
首先,需要安装 Flask-JWT-Extended 扩展:
pip install Flask-JWT-Extended
2.2 配置
请记住更改应用程序中的 JWT 密钥,并确保其安全。 JWT 使用此密钥进行签名,如果有人得到它,他们将能够创建您的 Web Flask 应用程序接受的任意令牌。
将'your-secret-key'
替换为自己的密钥。这个密钥将用于签署和验证JWT令牌。
from flask import Flask
from flask_jwt_extended import (JWTManager, jwt_required, create_access_token, get_jwt_identity)app = Flask(__name__)
# 用于签名JWT的密钥
app.config['JWT_SECRET_KEY'] = 'your-secret-key' # 初始化JWT扩展
jwt = JWTManager(app)
2.3 基本用法
这一 part 将介绍 flask_jwt_extended 的基础用法,以及展示 JWT 认证通过与不通过、获取token等的操作。
2.3.1 flask_jwt_extended 代码
代码释义:
- 定义了
/login
路由,用于用户登录并获取JWT令牌。在这个路由中,首先从请求中获取用户名和密码(这里是 “test” 和 “test”)。如果匹配成功,就使用create_access_token
函数生成JWT令牌,并返回给客户端。 - 定义了
/protected
路由,它是受保护的路由,只有在请求中包含有效的JWT令牌时才能访问。这是通过@jwt_required()
装饰器实现的。- 如果请求中没有有效的JWT令牌,访问该路由会返回未授权的响应。
- 如果令牌有效,路由会使用
get_jwt_identity()
函数获取JWT中的身份信息(在示例中为用户名)然后返回一个JSON响应,显示已登录的用户
注意:不要以明文形式存储密码,建议存储密码的哈希值,这里只为了作展示
# -*- coding: utf-8 -*-
# Name: basic_usage.pyfrom flask import (Flask, jsonify, request)
from flask_jwt_extended import (create_access_token, get_jwt_identity, jwt_required, JWTManager)app = Flask(__name__)# 设置 Flask-JWT-Extended 扩展
app.config["JWT_SECRET_KEY"] = "super-secret" # 修改为你自己的密钥
jwt = JWTManager(app)# 创建一个路由来验证您的用户并返回JWTs。create_access_token() 函数用于实际生成JWT。
@app.route("/login", methods=["POST"])
def login():username = request.json.get("username", None)password = request.json.get("password", None)if username != "test" or password != "test":return jsonify({"msg": "Bad username or password"}), 401access_token = create_access_token(identity=username)return jsonify(access_token=access_token)# 受保护的路由,需要JWT认证
@app.route("/protected", methods=["GET"])
@jwt_required() # 这个装饰器要求请求必须携带有效的JWT令牌
def protected():# 使用get_jwt_identity访问当前用户的身份current_user = get_jwt_identity()return jsonify(logged_in_as=current_user), 200if __name__ == "__main__":app.run()
2.3.2 无token请求
请求http://127.0.0.1:5000/protected,
- 因为 JWT 认证没通过,可以看到提示缺少授权的请求头,下面去申请一个token
2.3.3 申请token
传入data,username 和 password 都是test,
import requestsurl = 'http://127.0.0.1:5000/login'
data = {'username': 'test','password': 'test'
}
resp = requests.post(url, json=data)
print(resp.status_code)
print(resp.json())
代码运行效果如下:
在申请到 access_token之后,按照下面的形式,添加到请求头中
Authorization: Bearer <access_token>
2.3.4 有token请求
可以看到成功返回了用户的身份,
import requestsurl = 'http://127.0.0.1:5000/protected'
headers = {'Authorization': 'Bearer xxxxxxxxxxxxxxx'
}
resp = requests.get(url, headers=headers)
print(resp.status_code)
print(resp.json())
代码运行效果如下:
2.3.5 设置token有效期
设置JWT的过期时间是一种重要的安全措施,可以帮助确保令牌不会无限期有效,提高了应用程序的安全性。
注意,这里使用的是访问token。
方法一:
使用 app.config['JWT_ACCESS_TOKEN_EXPIRES']
来设置JWT的访问token默认过期时间为1小时。
# 设置ACCESS_TOKEN的默认过期时间为1小时
app.config['JWT_ACCESS_TOKEN_EXPIRES'] = timedelta(hours=1)
方法二:
当使用create_access_token
函数创建JWT令牌时,也可以通过传递expires_delta
参数来覆盖默认的过期时间,例如:
- 这将覆盖默认的过期时间,使得令牌在30分钟后过期。
from datetime import timedelta# 设置ACCESS_TOKEN的默认过期时间为30分钟
access_token = create_access_token(identity=username, expires_delta=timedelta(minutes=30))
当 token过期后,请求效果如下:
- 就会提示Token过期啦。
2.3.6 刷新token
这里要明确一下两个令牌概念,token分两种,具体可以查看下表
访问token(Access Token) | 刷新token(Refresh Token) | |
---|---|---|
用途 | 用于访问受保护的资源 | 用于获取新的访问token |
生命周期 | 默认为15分钟 | 默认为30天 |
显式指定生命周期 | JWT_ACCESS_TOKEN_EXPIRES | JWT_REFRESH_TOKEN_EXPIRES |
储存方式 | 在请求的头信息(Header)中的 “Authorization” 字段中 | 一般存储在服务器端的数据库 |
每个用户生成的刷新token和访问token是一一对应的,
当用户登录成功后,服务器会为该用户生成一对刷新token和访问token,并将它们关联到用户的身份(通常是用户的用户名或ID)。这样,每个用户都有自己唯一的刷新token和访问token。
刷新token用于获取新的访问token,以延长用户的会话时间。只有拥有有效的刷新token的用户才能获取新的访问token,而访问token则用于实际访问受保护的资源。
在上面的 flask_jwt_extended 代码中,修改了login函数,和添加了refresh函数,
# 创建一个路由来验证您的用户并返回JWTs。create_access_token() 函数用于实际生成JWT。
@app.route("/login", methods=["POST"])
def login():username = request.json.get("username", None)password = request.json.get("password", None)if username != "test" or password != "test":return jsonify({"msg": "Bad username or password"}), 401access_token = create_access_token(identity=username)refresh_token = create_refresh_token(identity=username)return jsonify(access_token=access_token, refresh_token=refresh_token)# 使用刷新token获取新的访问token
@app.route("/refresh", methods=["POST"])
@jwt_required(refresh=True) # 使用刷新token进行验证
def refresh():current_user = get_jwt_identity()access_token = create_access_token(identity=current_user)return jsonify(access_token=access_token)
一般来说,刷新token的有效时长会比访问token的有效时长更长,所以在访问token失效时候,可以使用刷新token去获得新的访问token。这样做的有几个优点:
- 用户体验:用户不需要重新输入用户名和密码,而只需提供有效的刷新token,就可以轻松地获取新的访问token。
- 安全性:如果用户的刷新token被泄露,攻击者仍然需要有效的用户名和密码才能获得新的访问token。这增加了安全性,因为攻击者无法仅凭刷新token获得新的访问token。
- 减少身份验证:用户不需要频繁地重新进行完整的身份验证,这可以减轻服务器的负担,并提高性能。
总之,/refresh
路由的主要目的是提供一种方便的方式来获取新的访问token,减少重复的登录操作,而不需要重新提供用户名和密码,同时提高了安全性。
2.3.6.1 获取两种token
在加上以上的代码之后,继续来看看运行效果。
访问 ‘http://127.0.0.1:5000/login’,可以得到以下结果。可以看到获得了两种token,
2.3.6.2 访问token过期
在访问token过期的情况下,访问如下所示,这个时候需要使用 刷新token 去获得新的 访问token。
2.3.6.3 获取新的访问token
当使用 刷新token去访问 refresh时候,服务端就会给我们返回 新的 访问token 。
关于JWT的介绍到此结束!
4. 改写后端API代码
以下是添加了用户认证与授权的 持久化数据存储的图书管理系统后端API代码,直接拿来就用!!!
- 校验账号密码这里,密码千万不要用明文,千万不要用明文!!!
# -*- coding: utf-8 -*-from datetime import timedeltafrom flask_sqlalchemy import SQLAlchemy
from flask import (Flask, jsonify, request)
from flask_jwt_extended import (JWTManager, jwt_required, create_access_token, get_jwt_identity, create_refresh_token)app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://root:123456@localhost/flask' # 替换为你的数据库 URI
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)app.config['JWT_SECRET_KEY'] = '1234567890' # 替换为你的密钥
app.config['JWT_ACCESS_TOKEN_EXPIRES'] = timedelta(minutes=10) # 设置JWT的默认过期时间为10分钟
# app.config['JWT_REFRESH_TOKEN_EXPIRES'] = timedelta(days=30)
jwt = JWTManager(app)# 定义Book模型类
class Book(db.Model):book_id = db.Column(db.Integer, primary_key=True, unique=True, nullable=False)title = db.Column(db.String(100), nullable=False)author = db.Column(db.String(50), nullable=False)# 创建一个路由来验证您的用户并返回JWTs。create_access_token() 函数用于实际生成JWT。
@app.route("/login", methods=["POST"])
def login():username = request.json.get("username", None)password = request.json.get("password", None)if username != "test" or password != "test":return jsonify({"msg": "Bad username or password"}), 401access_token = create_access_token(identity=username)refresh_token = create_refresh_token(identity=username)return jsonify(access_token=access_token, refresh_token=refresh_token)# 受保护的路由,需要JWT认证
@app.route("/protected", methods=["GET"])
@jwt_required() # 这个装饰器要求请求必须携带有效的JWT令牌
def protected():# 使用get_jwt_identity访问当前用户的身份current_user = get_jwt_identity()return jsonify(logged_in_as=current_user), 200# 使用刷新令牌获取新的访问令牌
@app.route("/refresh", methods=["POST"])
@jwt_required(refresh=True) # 使用刷新令牌进行验证
def refresh():current_user = get_jwt_identity()access_token = create_access_token(identity=current_user)return jsonify(access_token=access_token)# 获取所有书籍
@app.route("/books", methods=["GET"])
@jwt_required()
def get_all_books():books = Book.query.all()book_list = [{"id": book.book_id, "title": book.title, "author": book.author} for book in books]return jsonify(book_list), 200# 获取特定书籍
@app.route("/books/<int:book_id>", methods=["GET"])
@jwt_required()
def get_book(book_id):book = Book.query.get(book_id)if book:return jsonify({"id": book.book_id, "title": book.title, "author": book.author}), 200return jsonify({"error": "Book not found."}), 404# 创建新书籍
@app.route("/books", methods=["POST"])
@jwt_required()
def create_book():data = request.jsonnew_book = Book(title=data["title"], author=data["author"])db.session.add(new_book)db.session.commit()return jsonify({"id": new_book.book_id, "title": new_book.title, "author": new_book.author}), 201# 更新书籍信息
@app.route("/books/<int:book_id>", methods=["PUT"])
@jwt_required()
def update_book(book_id):book = Book.query.get(book_id)if book:data = request.jsonbook.title = data["title"]book.author = data["author"]db.session.commit()return jsonify({"id": book.book_id, "title": book.title, "author": book.author}), 200return jsonify({"error": "Book not found."}), 404# 删除书籍
@app.route("/books/<int:book_id>", methods=["DELETE"])
@jwt_required()
def delete_book(book_id):book = Book.query.get(book_id)if book:db.session.delete(book)db.session.commit()return "", 204return jsonify({"error": "Book not found."}), 404if __name__ == "__main__":app.run(debug=True)
5. 请求端代码
下面展示了用户如何使用 Python 的 requests
库来发送带有 JWT 令牌的请求,以获取受保护的数据:
- 如果访问token(Access Token)过期,Flask-JWT-Extended会自动返回错误响应,不需要手动验证过期。
- @jwt_required() # 这个装饰器会自动验证令牌的有效性和过期状态
- 这里使用 装饰器实现了当 访问token失效后通过 刷新token来生成一个新的 访问token
# -*- coding: utf-8 -*-import time
import requests# 定义 API 服务的基本 URL
BASE_URL = 'http://127.0.0.1:5000'
access_token = str()
refresh_token = str()def login(uname: str, passwd: str):login_url = f"{BASE_URL}/login"data = {"username": uname, "password": passwd}resp = requests.post(login_url, json=data)if resp.status_code == 200:return resp.json().get("access_token"), resp.json().get("refresh_token")else:print("Login failed.")return None, Nonedef refresh_access_token():global access_tokenrefresh_url = f"{BASE_URL}/refresh"headers = {"Authorization": f"Bearer {refresh_token}"}resp = requests.post(refresh_url, headers=headers)if resp.status_code == 200:access_token = resp.json().get("access_token")return Trueelse:print("Token refresh failed.")return Nonedef with_refresh(func):def wrapper(*arg, **kwargs):try:resp = func(*arg, **kwargs)if resp.status_code == 401:raise Exception(resp.json().get('msg'))else:return respexcept Exception as e:print(f"Error: {e}")new_access_token = refresh_access_token()if new_access_token:return func(*arg, **kwargs)else:print("Token refresh failed.")return Nonereturn wrapper@with_refresh
def get_protected_data():headers = {"Authorization": f"Bearer {access_token}"}protected_url = f"{BASE_URL}/books"return requests.get(protected_url, headers=headers)if __name__ == "__main__":access_token, refresh_token = login("test", "test")response = get_protected_data()print(response.json())time.sleep(62)response = get_protected_data()print(response.json())
在这个示例中,
-
login
函数负责发送登录请求并获取 JWT 令牌。 -
获得令牌后,
get_protected_data
函数使用获得的 JWT 令牌来获取受保护的数据。 -
如果 访问token失效了,则会调用
refresh_access_token
函数来获取新的 访问token
总结
这篇文章介绍了如何在 Flask
中使用 JSON Web Token(JWT) 进行用户认证和授权。它包括以下主要内容:
- JWT介绍:解释了JWT的基本原理,包括JWT的结构和生成验证过程。
- Flask-JWT-Extended简介:介绍了Flask-JWT-Extended扩展,它是一个用于在
Flask
应用程序中添加JWT支持的插件。 - JWT的基础用法:演示了如何使用 Flask-JWT-Extended 实现用户登录、JWT生成以及受保护路由的访问。还包括了设置JWT过期时间和刷新令牌的功能。
- 改写后端API代码:提供了一个示例,展示如何将JWT用户认证与授权集成到持久化数据存储的图书管理系统后端API中。
- 请求端代码:展示了如何使用Python的requests库发送带有JWT令牌的请求来获取受保护的数据,并在访问令牌过期时自动刷新令牌。
总的来说,这篇文章为小菜提供了使用JWT进行用户认证和授权的详细指南。
相关文章:

【实战Flask API项目指南】之七 用JWT进行用户认证与授权
实战Flask API项目指南之 用JWT进行用户认证与授权 本系列文章将带你深入探索实战Flask API项目指南,通过跟随小菜的学习之旅,你将逐步掌握 Flask 在实际项目中的应用。让我们一起踏上这个精彩的学习之旅吧! 前言 当小菜踏入Flask后端开发…...

鸿蒙LiteOs读源码教程+向LiteOS中添加一个简单的基于线程运行时的短作业优先调度策略
【⭐据说点赞收藏的都会收获好运哦👍】 一、鸿蒙Liteos读源码教程 鸿蒙的源码是放在openharmony文件夹下,openharmony下的kernel文件夹存放操作系统内核的相关代码和实现。 内核是操作系统的核心部分,所以像负责:资源管理、任…...
axios的使用与封装详细教程
目录 一、axios使用方式二、axios在main.js配置 一、axios使用方式 在 Spring Boot Vue 的项目中使用 Axios,你需要在 Vue 项目中安装 Axios 库,因为 Axios 是一个前端 JavaScript 库,用于发送 HTTP 请求和处理响应数据,而与 Sp…...

C++二叉搜索树
本章主要是二叉树的进阶部分,学习搜索二叉树可以更好理解后面的map和set的特性。 1.二叉搜索树概念 二叉搜索树的递归定义为:非空左子树所有元素都小于根节点的值,非空右子树所有元素都大于根节点的值,而左右子树也是二叉搜索树…...

elasticsearch索引按日期拆分
1.索引拆分原因 如果单个索引数据量过大会导致搜索变慢,而且不方便清理历史数据。 例如日志数据每天量很大,而且需要定期清理以往日志数据。例如原索引为sc_all_system_log,现按天拆分索引sc_all_system_log20220902,sc_all_syste…...
纯python实现大漠图色功能
大漠图色是一种自动化测试工具,可以用于识别屏幕上的图像并执行相应的操作。在Python中,可以使用第三方库pyautogui来实现大漠图色功能。具体步骤如下: 安装pyautogui库:在命令行中输入pip install pyautogui。导入pyautogui库&a…...
debounce and throtlle
debounce // 核心:单位时间内触发>1 则只执行最后一次。//excutioner 可以认为是执行器。执行器存在则清空,再赋值新的执行器。function debounce(fn, delay 500) {let excutioner null;return function () {let context this;let args arguments…...

四、数据库系统
数据库系统(Database System),是由数据库及其管理软件组成的系统。数据库系统是为适应数据处理的需要而发展起来的一种较为理想的数据处理系统,也是一个为实际可运行的存储、维护和应用系统提供数据的软件系统,是存储介…...

Linux中的高级IO
文章目录 1.IO1.1基本介绍1.2基础io的低效性1.3如何提高IO效率1.4五种IO模型1.5非阻塞模式的设置 2.IO多路转接之Select2.1函数的基本了解2.2fd_set理解2.3完整例子代码(会在代码中进行讲解)2.4优缺点 3.多路转接之poll3.1poll函数的介绍3.2poll服务器3.…...

项目管理之如何估算项目工作成本
在项目管理中,如何估算项目工作成本是一个关键问题。为了解决这个问题,我们可以采用自上而下的成本限额估算法和自下而上的成本汇总估算法。这两种方法各有优缺点,但都可以帮助我们准确地估算项目工作成本。 自上而下的成本限额估算法 自上…...
Redis主从复制基础概念
Redis主从复制:提高数据可用性和性能的策略 一、概述 Redis主从复制是一种常用的高可用性策略,通过将数据从一个Redis服务器复制到另一个或多个Redis服务器上,以提高数据的可用性和读取性能。当主服务器出现故障时,可以快速地切…...

图数据库Neo4j概念、应用场景、安装及CQL的使用
一、图数据库概念 引用Seth Godin的说法,企业需要摒弃仅仅收集数据点的做法,开始着手建立数据之间的关联关系。数据点之间的关系甚至比单个点本身更为重要。 传统的**关系数据库管理系统(RDBMS)**并不擅长处理数据之间的关系,那些表状数据模…...
路由器基础(四): RIP原理与配置
路由信息协议 (Routing Information Protocol,RIP) 是最早使用的距离矢量路由协议。因为路由是以矢量(距离、方向)的方式被通告出去的,这里的距离是根据度量来决定的,所以叫“距离矢量”。 距离矢量路由算法是动态路由算法。它的工作流程是:…...
红外遥控开发RK3568-PWM-IR
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言1.红外遥控的发送接收工作原理2.红外协议3.红外遥控系统框图4.遥控器添加方法4.1 记录键值4.2 添加键值总结前言 提示:这里可以添加本文要记录的大概内容: 1.红外遥控的发送接收工作原理 …...

go-sync-mutex
Sync Go 语言作为一个原生支持用户态进程(Goroutine)的语言,当提到并发编程、多线程编程时,往往都离不开锁这一概念。锁是一种并发编程中的同步原语(Synchronization Primitives),它能保证多…...

高并发系统设计
高并发系统通用设计方法 Scala-out 横向扩展,分散流量,分布式集群部署 缺点:引入复杂度,节点之间状态维护,节点扩展(上下线) Scala-up 提升单机性能,比如增加内存,增…...
Vue3-Pinia快速入门
1.安装pinia npm install pinia -save 2.在main.js中导入并使用pinia // 导入piniaimport { createPinia } from "pinia"; const pinia createPinia();//使用pinia app.use(pinia)app.mount(#app) 3.在src目录下创建包:store,表示仓库 4…...
Python算法——插入排序
插入排序(Insertion Sort)是一种简单但有效的排序算法,它的基本思想是将数组分成已排序和未排序两部分,然后逐一将未排序部分的元素插入到已排序部分的正确位置。插入排序通常比冒泡排序和选择排序更高效,特别适用于对…...
Java21新特性
目录 一、Java21新特性 1、字符串模版 2、scoped values 3、record pattern 4、switch格式匹配 5、可以在switch中使用when 6、Unnamed Classes and Instance Main Methods 7、Structured Concurrency 一、Java21新特性 1、字符串模版 字符串模版可以让开发者更简洁的…...

4 Tensorflow图像识别模型——数据预处理
上一篇:3 tensorflow构建模型详解-CSDN博客 本篇开始介绍识别猫狗图片的模型,内容较多,会分为多个章节介绍。模型构建还是和之前一样的流程: 数据集准备数据预处理创建模型设置损失函数和优化器训练模型 本篇先介绍数据集准备&am…...

关于iview组件中使用 table , 绑定序号分页后序号从1开始的解决方案
问题描述:iview使用table 中type: "index",分页之后 ,索引还是从1开始,试过绑定后台返回数据的id, 这种方法可行,就是后台返回数据的每个页面id都不完全是按照从1开始的升序,因此百度了下,找到了…...

ios苹果系统,js 滑动屏幕、锚定无效
现象:window.addEventListener监听touch无效,划不动屏幕,但是代码逻辑都有执行到。 scrollIntoView也无效。 原因:这是因为 iOS 的触摸事件处理机制和 touch-action: none 的设置有关。ios有太多得交互动作,从而会影响…...

OPENCV形态学基础之二腐蚀
一.腐蚀的原理 (图1) 数学表达式:dst(x,y) erode(src(x,y)) min(x,y)src(xx,yy) 腐蚀也是图像形态学的基本功能之一,腐蚀跟膨胀属于反向操作,膨胀是把图像图像变大,而腐蚀就是把图像变小。腐蚀后的图像变小变暗淡。 腐蚀…...

【笔记】WSL 中 Rust 安装与测试完整记录
#工作记录 WSL 中 Rust 安装与测试完整记录 1. 运行环境 系统:Ubuntu 24.04 LTS (WSL2)架构:x86_64 (GNU/Linux)Rust 版本:rustc 1.87.0 (2025-05-09)Cargo 版本:cargo 1.87.0 (2025-05-06) 2. 安装 Rust 2.1 使用 Rust 官方安…...
JavaScript基础-API 和 Web API
在学习JavaScript的过程中,理解API(应用程序接口)和Web API的概念及其应用是非常重要的。这些工具极大地扩展了JavaScript的功能,使得开发者能够创建出功能丰富、交互性强的Web应用程序。本文将深入探讨JavaScript中的API与Web AP…...
深入理解Optional:处理空指针异常
1. 使用Optional处理可能为空的集合 在Java开发中,集合判空是一个常见但容易出错的场景。传统方式虽然可行,但存在一些潜在问题: // 传统判空方式 if (!CollectionUtils.isEmpty(userInfoList)) {for (UserInfo userInfo : userInfoList) {…...
安卓基础(Java 和 Gradle 版本)
1. 设置项目的 JDK 版本 方法1:通过 Project Structure File → Project Structure... (或按 CtrlAltShiftS) 左侧选择 SDK Location 在 Gradle Settings 部分,设置 Gradle JDK 方法2:通过 Settings File → Settings... (或 CtrlAltS)…...
Spring Security 认证流程——补充
一、认证流程概述 Spring Security 的认证流程基于 过滤器链(Filter Chain),核心组件包括 UsernamePasswordAuthenticationFilter、AuthenticationManager、UserDetailsService 等。整个流程可分为以下步骤: 用户提交登录请求拦…...

VisualXML全新升级 | 新增数据库编辑功能
VisualXML是一个功能强大的网络总线设计工具,专注于简化汽车电子系统中复杂的网络数据设计操作。它支持多种主流总线网络格式的数据编辑(如DBC、LDF、ARXML、HEX等),并能够基于Excel表格的方式生成和转换多种数据库文件。由此&…...
数据库正常,但后端收不到数据原因及解决
从代码和日志来看,后端SQL查询确实返回了数据,但最终user对象却为null。这表明查询结果没有正确映射到User对象上。 在前后端分离,并且ai辅助开发的时候,很容易出现前后端变量名不一致情况,还不报错,只是单…...