【Python开发】Flask开发实战:个人博客(三)

news2025/7/22 5:21:58

Flask开发实战:个人博客(三)

在【Python开发】Flask开发实战:个人博客(一) 中,我们已经完成了 数据库设计、数据准备、模板架构、表单设计、视图函数设计、电子邮件支持 等总体设计的内容。

在【Python开发】Flask开发实战:个人博客(二)中,我们一起实现了 显示文章列表、博客信息、文章内容和评论 等功能。

那么,本篇文章将会介绍如何 初始化博客、利用 Flask-Login 管理用户认证、使用 CSRFProtect 实现 CSRF 保护

1.安全存储密码

创建管理员用户需要存储用户名和密码,密码的存储需要特别注意。密码不能直接以明文的形式存储在数据库中,因为一旦数据库被窃取或是被攻击者使用暴力破解或字典法破解,用户的账户、密码将被直接泄露。如果发生泄漏,常常会导致用户在其他网站上的账户处于危险状态,因为通常用户会在多个网站使用同一个密码。一般的做法是不存储密码本身,而是存储通过密码生成的散列值(hash)。每一个密码对应着独一无二的散列值,从而避免明文存储密码。

如果只是简单地计算散列值,攻击者可以使用彩虹表的方式逆向破解密码。这时我们需要加盐计算散列值。加盐后,散列值的随机性会显著提高。但仅仅把盐和散列值连接在一起可能还不够,我们还需要使用 HMAC(hash-based message authentication code) 来重复计算很多次(比如 5000 次)最终获得派生密钥,这会增大攻击者暴力破解密码的难度,这种方式被称为 密钥扩展key stretching)。

在密码学中,(salt)是一串随机生成的字符,用来增加散列值计算的随机性。经过这一系列处理后,即使攻击者获取到了密码的散列值,也无法逆向获取真实的密码值。在生产环节中,尽管对密码加密存储安全性很强,仍然需要使用安全的 HTTP 以加密传输数据,避免密码在传输过程中被截获。

Werkzeug 在 security 模块中提供了一个 generate_password_hash(password,method=‘pbkdf2:sha256’,salt_length=8) 函数用于为给定的密码生成散列值,参数 method 用来指定计算散列值的方法,salt_length 参数用来指定盐(salt)的长度。security 模块中的 check_password_hash(pwhash,password) 函数接收散列值(pwhash)和密码(password)作为参数,用于检查密码散列值与密码是否对应。

>>> from werkzeug.security import generate_password_hash, check_password_hash
>>> password_hash = generate_password_hash('cat')
>>> password_hash
'pbkdf2:sha256:50000$mIeMzTvb$ba3c0a274c6b53fda2ab39f864254dfb0a929848b7ec99f81e3bf721d8860fdc'
>>> check_password_hash(password_hash, 'dog')
False
>>> check_password_hash(password_hash, 'cat')
True
>>> password_hash = generate_password_hash('cat')
>>> password_hash
'pbkdf2:sha256:150000$AITKk6jv$5c0b732535cae83677fdf2e666153f82b5db30e6f40ec7a625678ad2b5f4ad25'

generate_password_hash() 函数生成的密码散列值的格式如下:

method$salt$hash

因为在计算散列值时会加盐,而盐是随机生成的,所以即使两个用户的密码相同,最终获得的密码散列值也是不同的。我们没法从密码散列值逆向获取密码,但是如果密码、计算方法、盐相同,最终获得的散列值结果也会是相同的,所以 check_password_hash() 函数会根据密码散列值中的方法、盐重新对传入的密码进行散列值计算,然后对比散列值。

from werkzeug.security import generate_password_hash, check_password_hash
class User(db.Model):
	...
	password_hash = db.Column(db.String(128))
	...
	def set_password(self, password):
		self.password_hash = generate_password_hash(password)
	def validate_password(self, password):
		return check_password_hash(self.password_hash, password)

set_password() 方法用来设置密码,它接收密码的原始值作为参数,将密码的散列值设为 password_hash 的值。validate_password() 方法用于验证密码是否和对应的散列值相符,返回布尔值。

2.使用Flask-Login管理用户认证

博客程序需要根据用户的身份开放不同的功能,对于程序使用者——管理员来说,他可以撰写文章、管理博客;而普通的用户(匿名用户)则只能阅读文章、发表评论。为了让程序识别出用户的身份,我们需要添加用户认证功能。具体来说,使用用户名和密码登入博客程序的用户被视为管理员,而未登录的用户则被视为匿名用户。

扩展 Flask-Login 为 Flask 提供了用户会话管理功能,使用它可以轻松的处理用户登录、登出等操作。

extensions.py 脚本中实例化扩展提供的 LoginManager 类,创建一个 login_managerlogin 对象。

from flask_login import LoginManager
...
login_manager = LoginManager(app)

然后在程序包的工厂函数中对 login 对象调用 init_app() 方法进行初始化扩展

login_manager.init_app(app)

Flask-Login 要求表示用户的类必须实现下表中所示的这几个属性和方法,以便用来判断用户的认证状态。
在这里插入图片描述
通过对用户对象调用各种方法和属性即可判断用户的状态,比如是否登录等。方便的做法是让用户类继承 Flask-Login 提供的 UserMixin 类,它包含了这些方法和属性的默认实现。

from flask_login import UserMixin

class Admin(db.Model, UserMixin):
	...

UserMinxin 表示通过认证的用户,所以 is_authenticatedis_active 属性会返回 True,而 is_anonymous 则返回 Falseget_id() 默认会查找用户对象的 id 属性值作为 id,而这正是我们的 Admin 类中的主键字段。

使用 Flask-Login 登入/登出某个用户非常简单,只需要在视图函数中调用 Flask-Login 提供的 login_user()logout_user() 函数,并传入要登入/登出的用户类对象。在这两个函数背后,Flask-Login 使用 Flask 的 session 对象将用户的 id 值存储到用户浏览器的 cookie 中(名为 user_id),这时表示用户被登入。相对来说,登出则意味着在用户浏览器的 cookie 中删除这个值。默认情况下,关闭浏览器时,通过 Flask 的 session 对象存储在客户端的 session cookie 会被删除,所以用户会登出。

另外,Flask-Login 还支持记住登录状态,通过在 login_user() 中将 remember 参数设为 True 即可实现。这时 Flask-Login 会在用户浏览器中创建一个名为 remember_tokencookie,当通过 session 设置的 user_id cookie 因为用户关闭浏览器而失效时,它会重新恢复 user_id cookie 的值。

为了防止破坏 Flask-Login 提供的认证功能,我们在视图函数中操作 session 时要避免使用 user_idremember_token 作为键。remember_token cookie 的默认过期时间为 365 天。你可以通过配置变量 REMEMBER_COOKIE_DURATION 进行设置,设为 datetime.timedelta 对象即可。

2.1 获取当前用户

那么我们如何判断用户的认证状态呢?答案是使用 Flask-Login 提供的 current_user 对象。它是一个和 current_app 类似的代理对象(Proxy),表示当前用户。调用时会返回与当前用户对应的用户模型类对象。因为 session 中只会存储登录用户的 id,所以为了让它返回对应的用户对象,我们还需要设置一个用户加载函数。这个函数需要使用 login_manager.user_loader 装饰器,它接收用户 id 作为参数,返回对应的用户对象。

@login_manager.user_loader
def load_user(user_id):
    from bluelog.models import Admin
    user = Admin.query.get(int(user_id))
    return user

现在,当我们调用 current_user 时,Flask-Login 会调用用户加载函数并返回对应的用户对象。如果当前用户已经登录,会返回 Admin 类实例;如果用户未登录,current_user 默认会返回 Flask-Login 内置的 AnonymousUserMixin 类对象,它的 is_authenticatedis_active 属性会返回 False,而 is_anonymous 属性则返回 True

current_user 存储在请求上下文堆栈上,所以只有激活请求上下文程序的情况下才可以使用,比如在视图函数中或是模板中调用。

最终,我们可以通过对 current_user 对象调用 is_authenticated 等属性来判断当前用户的认证状态。它也和我们自定义的模板全局变量一样注入到了模板上下文中,可以在所有模板中使用,所以我们可以在模板中根据用户状态渲染不同的内容

2.2 登入用户

个人博客的登录链接可以放在次要的位置,因为只有博客作者才会真正用到它。我们把它放到页脚,并根据用户的状态来选择渲染出不同的链接。

<small>
	{% if current_user.is_authenticated %}
	<!-- 如果用户已经登录,显示下面的“登出”链接-->
	<a href="{{ url_for('auth.logout', next=request.full_path) }}">Logout</a>
	{% else %}
	<!-- 如果没有登录,则显示下面的“登录”按钮 -->
	<a href="{{ url_for('auth.login', next=request.full_path) }}">Login</a>
	{% endif %}
</small>

通过 current_useris_authenticated 值判断用户是否登录,如果用户已登录(is_authenticatedTrue)就渲染注销按钮,否则就渲染登录按钮。按钮中的 URL 分别指向用于登录和登出的 loginlogout 视图,url_for() 函数中加入的 next 参数用来存储当前页面的路径,以便在执行登录或登出操作后将用户重定向回上一个页面。

from flask_login import login_user
from bluelog.forms import LoginForm
from bluelog.models import Admin
from bluelog.utils import redirect_back

...

@auth_bp.route('/login', methods=['GET', 'POST'])
def login():
    if current_user.is_authenticated:
        return redirect(url_for('blog.index'))

    form = LoginForm()
    if form.validate_on_submit():
        username = form.username.data
        password = form.password.data
        remember = form.remember.data
        admin = Admin.query.first()
        if admin:
            # 验证用户名和密码
            if username == admin.username and admin.validate_password(password):
                login_user(admin, remember)  # 登入用户
                flash('Welcome back.', 'info')
                return redirect_back()  # 返回上一个页面
            flash('Invalid username or password.', 'warning')
        else:
            flash('No account.', 'warning')
    return render_template('auth/login.html', form=form)

登录视图负责渲染 login.html 模板和验证登录表单。在函数一开始,为了避免已经登录的用户不小心访问这个视图,我们添加一个if判断将已经登录的用户重定向到首页。

与其他表单处理流程相同,当用户提交表单且数据通过验证后,我们分别从表单中获取用户名(username)、密码(password)和 “记住我”(remember)字段的数据。接着,从数据库中查询出 Admin 对象,判断 username 的值,并使用 Admin 类中的 validate_password() 方法验证密码。如果通过验证就调用 login_user() 方法登录用户,传入用户对象和 remember 字段的值作为参数,最后使用 redirect_back() 函数重定向回上一个页面;如果用户名和密码验证出错就发送错误提示,并渲染模板。另外,如果 Admin 对象不存在,就发送一个提示消息,然后重新渲染表单。

登录表单 LoginForm 在新创建的 login.html 模板中使用 Bootstrap-Flask 提供的 render_form() 宏渲染。为了编写一个更简单的登录页面,我们打算不在登录页面显示页脚,因为我们在基模板中为页脚的代码定义了 footer 块,所以在登录页面模板只需要定义这个块并留空就可以覆盖基模板中的对应内容。

{% extends 'base.html' %}
{% from 'bootstrap/form.html' import render_form %}

{% block title %}Login{% endblock %}

{% block content %}
    <div class="container h-100">
        <div class="row h-100 page-header justify-content-center align-items-center">
            <h1>Log in</h1>
        </div>
        <div class="row h-100 justify-content-center align-items-center">
            {{ render_form(form, extra_classes='col-6') }}
        </div>
    </div>
{% endblock %}

{% block footer %}{% endblock %}

在这里插入图片描述

2.3 登出用户

注销登录比登录还要简单,只需要调用 Flask-Login 提供的 logout_user() 函数即可。这会登出用户并清除 session 中存储的用户 id 和 “记住我” 的值。

from flask_login import logout_user
...
@auth_bp.route('/logout')
@login_required
def logout():
	logout_user()
	flash('Logout success.', 'info')
	return redirect_back()

2.4 视图保护

程序中的许多操作要求用户登录后才能进行,因此我们要把这些需要登录才能访问的视图 “保护” 起来。如果用户访问了某个需要认证才能访问的资源,我们不会返回对应的响应,而是把程序重定向到登录页面。

视图保护可以使用 Flask-Login 提供的 login_required 装饰器实现。在需要登录才能访问的视图前附加这个装饰器,比如博客设置页面。

当为视图函数附加多个装饰器时,route() 装饰器应该置于最外层。

from flask_login import login_required

@admin_bp.route('/settings')
@login_required
def settings():
	...
	return render_template('admin/settings.html')

当未登录的用户访问使用了 login_required 装饰器的视图时,程序会自动重定向到登录视图,并闪现一个消息提示。在此之前,我们还需要在 extension.py 脚本中使用 login_manager 对象的 login_view 属性设置登录视图的端点值(包含蓝本名的完整形式)。

login_manager = LoginManager(app)
...
login_manager.login_view = 'auth.login'
login_manager.login_message_category = 'warning'

使用可选的 login_message_category 属性可以设置消息的类别,默认类别为 “message”。另外,使用可选的 login_message 属性设置提示消息的内容,默认消息内容为“Please log in to access this page.”

当用户访问某个被保护的 URL 时,在重定向后的登录 URL 中,Flask-Login 会自动附加一个包含上一个页面 URL 的 next 参数,所以我们只需要使用 redirect_back() 函数就可以将登录成功后的用户重定向回上一个页面。

当在未登录状态下访问设置页面 http://localhost:5000/admin/settings 时,程序会重定向到登录页面,并显示提示消息,URL 中包含上一个页面的 next 参数。

在这里插入图片描述

仔细观察地址栏,你会看到附加的 next 参数包含上一个页面的地址,我们经常在上网时在地址栏发现类似的参数,比如 ReturnUrlRedirectUrl 等。当我们登录后,程序会重定向回我们想要访问的设置页面。

有些时候,你会希望为整个蓝本添加登录保护。比如,管理后台的所有页面都需要登录后才能访问,也就是说,我们需要为所有 admin 蓝本中的视图函数附加 login_required 装饰器。有一个小技巧可以避免这些重复:为 admin 蓝本注册一个 before_request 处理函数,然后为这个函数附加 login_required 装饰器。因为使用 before_request 钩子注册的函数会在每一个请求前运行,所以这样就可以为该蓝本下所有的视图函数添加保护,函数内容可以为空。

@admin_bp.before_request
@login_required
def login_protect():
	pass
  • 虽然这个技巧很方便,但是为了避免在书中单独给出视图函数代码时造成误解,Bluelog程序中并没有使用这个技巧。
  • 如果没有使用这个技巧,那么 admin 蓝本下的所有视图都需要添加 login_required 装饰器,否则会导致博客资源被匿名用户修改。

3.使用CSRFProtect实现CSRF保护

CSRF攻击,全称为 Cross-site request forgery,中文名为 跨站请求伪造,也被称为 One Click Attack 或者 Session Riding,通常缩写为 CSRF 或者 XSRF,是一种对网站的恶意利用。XSS 主要是利用站点内的信任用户,而 CSRF 则通过伪装来自受信任用户的请求,来利用受信任的网站。与 XSS 相比,CSRF 更具危险性。攻击者盗用用户身份,发送恶意请求。比如:模拟用户发送邮件,发消息,以及支付、转账等。

在这里插入图片描述
博客管理后台会涉及对资源的局部更新和删除操作,这时我们就要考虑到 CSRF 保护问题。为了应对 CSRF 攻击,当需要创建、修改和删除数据时,我们需要将这类请求通过 POST 方法提交,同时在提交请求的表单中添加 CSRF 令牌。对于删除和某些修改操作来说,单独创建表单类的流程太过烦琐,我们可以使用 Flask-WTF 内置的 CSRFProtect 扩展为这类操作实现更简单和完善的 CSRF 保护。

CSRFProtect 是 Flask-WTF 内置的扩展,也是 Flask-WTF 内部使用的 CSRF 组件,单独使用可以实现对程序的全局 CSRF 保护。它主要提供了生成和验证 CSRF 令牌的函数,方便在不使用 WTForms 表单类的情况下实现 CSRF 保护。因为我们已经安装了 Flask-WTF,所以可以直接使用它。首先在 extensions.py 脚本中实例化 Flask-WTF 提供的 CSRFProtect 类。

from flask_wtf.csrf import CSRFProtect
...
csrf = CSRFProtect()
...

在程序包的构造文件中初始化扩展 CSRFProtect

from bluelog.extensions import csrf

def create_app(config_name=None):
	...
	register_extensions(app)
	return app
	
def register_extensions(app):
	...
	csrf.init_app(app)

CSRFProtect 在模板中提供了一个 csrf_token() 函数,用来生成 CSRF 令牌值,我们直接在表单中创建这个隐藏字段,将这个字段的 name 值设为 csrf_token。下面是用来删除文章的表单示例:

<form method="post" action="{{ url_for('.delete_post', post_id=post.id) }}">
	<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
	<input type="submit" value="Delete Post"/>
</form>

在对应的 delete_post 视图中,我们直接执行相关删除操作,CSRFProtect 会自动获取并验证 CSRF 令牌。注意,在 app.route() 装饰器中使用 methods 参数限制仅监听 POST 请求。

@app.route('/post/delete/<id>', methods=['POST'])
def delete_post(id):
	post = Post.query.get(id)
	post.delete()
	return redirect(url_for('index'))

默认情况下,当令牌验证出错或过期时,程序会返回 400 错误,和 Werkzeug 内置的其他 HTTP 异常类一样,CSRFError 将错误描述保存在异常对象的 description 属性中。

如果你想将与 CSRF 相关的错误描述显示在模板中,那么你可以在 400 错误处理函数中将异常对象的 description 属性传入模板,也可以单独创建一个错误处理函数捕捉令牌出错时抛出的 CSRFError 异常。

from flask_wtf.csrf import CSRFError

def register_errors(app):
	...
	@app.errorhandler(CSRFError)
	def handle_csrf_error(e):
		return render_template('400.html', description=e.description), 400

这个错误处理函数仍然使用 app.errorhandler 装饰器注册,传入 flask_wtf.csrf 模块中的 CSRFError 类。这个错误处理函数返回 400 错误响应,通过异常对象的 description 属性获取内置的错误消息(英文),传入模板 400.html 中。在模板中,我们渲染这个错误消息,并为常规 400 错误设置一个默认值。

<p>{{ description|default('Bad Request') }}</p>

在实际应用中,除了使用内置的错误描述,更合适的方法是自己编写错误描述信息。默认的错误描述为 “Invalid CSRF token.” 和 “The CSRF token is missing.” 因为包含太多术语,不容易理解,所以在实际的程序中,我们应该使用更简单的错误提示,比如 “会话过期或失效,请返回上一页面重试”。


敬请关注后续更新!

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

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

相关文章

公众号裂变拉新,以婴儿辅食为诱饵,实现低成本获客!

大家好~我是娜娜 今天来给大家拆解一个关于食品行业精选公众号增长案例&#xff0c;通过公众号裂变拉新&#xff0c;任务拉新人数5000&#xff0c;留存率达到85%&#xff0c;活动裂变率达到1100.86%。活动数据也还在持续的上升当中。 该公众号的目标人群是新手爸妈&#xff0…

【Java】SpringBoot应用简单示例

SpringBoot应用简单示例SpringBoot应用简单示例HelloWorld搭建项目ResponseBody的作用ComponentScan排除扫描beanSpringBoot集成日志SpringBoot日志初始化原理消息转换器拦截器过滤器操作数据库Spring Data JpaDruid数据源Mybatis-Plus事务处理操作缓存AOP相关概念栗子定时任务…

C语言解析JSON源码

它与 XML 的地位差不多&#xff0c;但就笔者而言&#xff0c;笔者更喜欢 JSON 的风格&#xff0c;因为它更符合我们的思维习惯&#xff0c;同样一份数据&#xff0c;JSON 格式的就是比 XML 要清晰明了一些。 最近笔者需要在 C语言 上解析 JSON 格式&#xff0c;在网上一顿找&am…

XC5VLX30T-2FF323I Virtex-5 LXT FPGA IC 产品参数

概述 Virtex-5 FPGA有-3&#xff0c;-2&#xff0c;-1速度等级&#xff0c;其中-3具有最高的性能。Virtex-5 FPGA直流和交流特性指定为商业和工业级别。除工作温度范围外&#xff0c;除非另有说明&#xff0c;所有直流和交流电气参数对于特定转速等级是相同的(即-1转速等级的工…

一夜登顶GitHub!字节内网数据结构与算法刷题笔记,看完直呼卧槽

网络上流传着一句段子“程序员两条腿&#xff0c;一条是算法&#xff0c;一条是英文&#xff0c;想跑的更远&#xff0c;这两条腿都不能弱”。英文&#xff0c;我们暂且不谈&#xff0c;我们先来谈谈算法。 算法之难&#xff0c;在于将精巧的逻辑&#xff0c;通过合适的数据结…

2 分钟,教你用 Serverless 每天给女朋友自动发土味情话

作者&#xff1a;安可 Serverless 简介 Serverless&#xff0c;中文意思是 “无服务器”&#xff0c;所谓的无服务器并非是说不需要依靠服务器等资源&#xff0c;而是说开发者再也不用过多考虑服务器的问题&#xff0c;可以更专注在产品代码上&#xff0c;同时计算资源也开始…

如何根据自己的SCI论文,匹配适合的期刊? - 易智编译EaseEditing

如何选择合适的目标期刊是需要慎重对待的问题&#xff0c;它决定了你论文的发表速度和被认可度。 可以遵循以下几个步骤来考虑&#xff1a; 1、从你论文的参考文献中选择合适的期刊&#xff08;如果引用文献较少&#xff0c;也可以从引文的参考文献中进行筛选&#xff09;&…

成功解决:ModuleNotFoundError: No module named ‘amp_C‘

在使用transformers时&#xff0c;在调用Trainer的时候遇到了这个问题&#xff0c;原因是apex包有问题&#xff0c; 这里有解决apex安装包的多一些教程 https://blog.csdn.net/Xidian185/article/details/122745427 https://blog.csdn.net/weixin_45225975/article/details/119…

倍福TwinCAT3中使用久同伺服

目录 一、测试设备说明 二、伺服通电和参数设置 1、恢复出厂参数设置 2、恢复出厂&#xff0c;重启后 3、伺服自己点动操作 4、增益、刚度调整 5、伺服零位设定 6、伺服转动一圈编码器脉冲量设定 7、参数保存 三、伺服操作面板 四、TwinCAT3工程配置 1、XML文件 2、…

【元宇宙欧米说】打造艺术与技术构建的交互式数字旅程

Web3 to Earn项目如何扩大应用功能和场景&#xff1f;在Web3时代怎么才能以更新颖、有趣的方式追赶潮流&#xff1f;各Web3领域项目及应用如何进行功能外延以满足用户需求&#xff1f; 11月17日晚上九点&#xff0c;ZenCats项目管理员Fred将以“打造艺术与技术构建的交互式数字…

编码格式转换方法

今天项目上遇到了需要将 SJIS(Shift-JIS) 格式与 UTF8 格式相互转换问题。 首先看一个编码格式问题引发的乱码现象&#xff0c;新建下面的文本文档&#xff0c;然后更名为 test.bat。 echo off echo test chinese character view 测试中文字符显示 pause双击运行 用 chcp 查…

正版授权| iObit Uninstaller 12 Pro 专业卸载器工具

前言 专业的Win系统卸载程序&#xff0c;它可以轻松删除不需要的程序&#xff0c;插件和Windows应用程序&#xff0c;还可以对电脑旧的应用一键更新。安装监视器会检测并记录安装中的所有系统更改&#xff0c;以确保在将来彻底卸载时可以还原所有更改。 功能特点 安装监视器 …

1053 Path of Equal Weight

Given a non-empty tree with root R, and with weight Wi​ assigned to each tree node Ti​. The weight of a path from R to L is defined to be the sum of the weights of all the nodes along the path from R to any leaf node L. Now given any weighted tree, you a…

QT获取计算机硬件信息

一、项目介绍 本文介绍利用QProcess获取计算机的CPU、主板、硬盘等电脑相关硬件信息。 windows提供了“wmic”&#xff08;Windows Management Instrumentation&#xff0c;Windows管理工具&#xff09;&#xff0c;提供了从命令行接口和批命令脚本执行系统管理的支持。可以打…

基于多个openEuler物理机执行mugen测试脚本

【原文链接】基于多个openEuler物理机执行mugen测试脚本 mugen脚本中有的脚本执行需要使用多个物理机&#xff0c;针对此场景&#xff0c;这里以需要两个物理机为例&#xff08;用openEuler虚拟机模拟物理机&#xff09; &#xff08;1&#xff09;首先安装两台openEuler虚拟…

【C++】C++基础知识(一)---基本概念

C基础知识&#xff08;一&#xff09;1. 输出“HelloWorld!”2. 添加注释3. 关键字4. 标识符5. 变量6. 常量1. 输出“HelloWorld!” 在visual studio中输出“HelloWorld!”。代码实现如下&#xff1a; #include <iostream> using namespace std;int main() {cout <&…

unity搭建xlua和emmy_lua的debug环境

配置步骤 1 环境 1.1 vscode 安装emmy_lua 1.2 安装对应的lua版本 1.3 安装java8并配置环境 1.4 emmy_lua的github上下载emmy_lua的64位版本&#xff0c;解压放到工程目录client\Tools\EmmyLua\ 下载地址&#xff1a;https://github.com/EmmyLua/EmmyLuaDebugger/release…

「Redis数据结构」动态字符串(SDS)

「Redis数据结构」动态字符串&#xff08;SDS&#xff09; 文章目录「Redis数据结构」动态字符串&#xff08;SDS&#xff09;[toc]一、前言二、概述三、C字符串与SDS的区别获取字符串长度复杂度杜绝缓冲区溢出减少修改字符串时的内存分配次数二进制安全兼容部分C字符串函数参考…

【PyTorch】Transforms基本使用

文章目录二、Transforms基本使用1、Transforms的结构及用法1.1 如何使用1.2 TensorBoard查看2、常用的Transforms2.1 ToTensor2.2 Normalize2.3 Resize2.4 Compose2.5 RandomCrop二、Transforms基本使用 Transforms主要是对特定格式的图片进行一些变化。 1、Transforms的结构及…

展锐UIS8310 CAT4物联网模块简介

1.简介 UIS8310是一个高度集成的应用处理器&#xff0c;支持TDD-LTE、FDD-LTE和WCDMA、GSM/GPRS/EDGE制式&#xff0c;并且支持LPDDR2。它的AP处理器是单核ARM CortexTM A7 1GHz&#xff0c;旨在为物联网提供经济高效、低功耗和高性能的解决方案。 UIS8310经过特别优化的架构可…