当前位置: 首页 > news >正文

Flask之电子邮件

  前言:本博客仅作记录学习使用,部分图片出自网络,如有侵犯您的权益,请联系删除 

目录

一、使用Flask-Mail发送电子邮件

1.1、配置Flask-Mail

1.2、构建邮件数据

1.3、发送邮件

二、使用事务邮件服务SendGrid

2.1、注册SendGrid

2.2、SendGrid SMTP转发

2.3、SendGrid Web API转发

三、电子邮件进阶

3.1、提供HRML正文

3.2、使用Jinja2模板组织邮件正文

3.3、异步发送邮件

致谢


示例邮件:

邮件字段字段值
发信方(Sender)Greygrey@helloflask.com
收信方(To)Zornzorn@example.com
邮件主体(Subject)Hello,World!
邮件正文(Body)Across the Great Wall we can reach every corner in the world.

一、使用Flask-Mail发送电子邮件

扩展Flask-Mail包装了Python标准库中的smtplib包,简化了在Flask程序中发送电子邮件的过程。

 pip install flask-mail

实例化Flask-Mail提供的Mail类并传入程序实例以完成初始化:

 from flask_mail import Mail​app = Flask(__name__)...mail = Mail(app)

1.1、配置Flask-Mail

Flask-Mail通过连接SMTP(Simple Mail Transfer Protocal,简单邮件传输协议服务器来发送邮件。在开发和测试阶段,我们使用邮件服务提供商的SMTP服务器(比如Gmail),这时我们需要对Flask-Mail进行配置。

Flask-Mail常用配置变量:

配置键说明默认值
MAIL_SERVER用于发送邮件的SMTP服务器localhost
MAIL_PORT发送端口25
MAIL_USE_TLS是否使用STRTTLSFalse
MAIL_USE_SSL是否使用SSL/TLSFalse
MAIL_USERNAME发送服务器的用户名None
MAIL_PASSWORD发送服务器的密码None
MAIL_DEFAULT_SENDER默认的发信人None

对发送的邮件进行加密可以避免在发送过程中被第三方截获和篡改。SSL(Security Socket Layer,安全套接字层)和TLS(Transport Layer Security,传输层安全)是两种常用的电子邮件安全协议。TLS继承了SSL,并在SSL的基础上做了一些改进。通过将MAIL_USE_SSL设置为True开启。STARTTLS是另一种加密方式,它会对不安全的连接进行升级。

 # 1、SSL/TLS加密:MAIL_USE_SLL = TrueMAIL_PORT = 465​# 2、STARTTLS加密:MAIL_USE_TLS = TrueMAIL_PORT = 587

(当不对邮件进行加密时,邮件服务器的端口使用默认的25端口)

常见的电子邮箱服务提供商的STMP配置信息:

电子邮件服务提供商MAIL_SERVERMAIL_USERNAMEMAIL_PASSWORD额外步骤
Gmailsmtp.gmail.com邮箱地址邮箱密码开启"Allow less secure apps",在本地设置VPN代理
QQ邮箱smtp.qq.com邮箱地址授权码开启SMTP服务并获取授权码
新浪邮件smtp.sina.com邮箱地址邮箱密码开启SMTP服务
163邮箱smtp.163.com邮箱地址授权码开启SMTP服务并设置授权码
Outlook/Hotmailsmtp.live.com或smtp.office365.com邮箱地址邮箱密码

(163邮箱的SMTP服务不支持STARTTLS,需要使用SSL/TLS加密。就是将MAIL_USE_SSL设为True,MAIL_PORT设为465)

Gmail、Outlook、QQ邮箱等这类服务被称为EPA(Email Service Procider),适用于个人业务使用,不适合用来发送事务邮件。对于需要发送大量邮件的事务性邮件任务,更好的选择是使用自己配置的SMTP服务器或是使用类似SendGrid、Mailgun的事务邮件服务提供商。

在程序中,随着配置的逐渐增多,我们改用app.config对象的update()方法来加载配置:

 import osfrom flask import Flaskfrom flask_mail import Mail​app = Flask(__name__)​app.config.update(MAIL_SERVER=os.getenv('MAIL_SERVER'),MAIL_PORT=587,MAIL_USE_TLS=True,MAIL_USERNAME=os.getenv('MAIL_USERNAME'),MAIL_PASSWORD=os.getenv('MAIL_PASSWORD'),MAIL_DEFAULT_SENDER=('Grey Li', os.getenv('MAIL_USERNAME')))mail = Mail(app)

在我们的配置中,邮箱账户和密码属于敏感信息,不能直接写在脚本中,所以设置为从系统环境变量中获取。

1.2、构建邮件数据

下面借助Python Shell演示。邮件通过从Flask-Mail中导入的Message类表示,而发信功能通过我们在程序包的构建文件中创建的mail对象实现:

 $ flask shell>>> from flask_mail import Message>>> from app import mail

一封邮件至少包含主题、收件人、正文、发信人这几个。发信人在前用MAIL_DEFUALT_SENDER配置变量指定过了,剩下的分别通过Message类的构造方法中的subject、recipients、body关键字传入参数,其中recipients是包含一电子邮件地址的列表

 >>> message = Message(subject='Hello,World!',recipients=['Zorn <zorn@example.com>'],body='Across the Great Wall we can reach every corner in the world.')

(和发信人类似,收信人字符串有两种新式:'Zorn zorn@example.com'或'zorn@example.com')

1.3、发送邮件

通过对mail对象调用send()方法,传入我们在上面构建的邮件对象即可发送邮件:

 >>> mail.send(message)

完整的发送示例代码:

 from flask import Flaskfrom flask_mail import Mail,Message...message = Message(subject='Hello,World!',recipients=['Zorn <zorn@example.com>'],body='Across the Great Wall we can reach every corner in the world.')mail.send(message)

为了方便重用,我们把这些代码包装成一个通用的发信函数send_mail():

...
def send_mail(subject,to,body):message = Message(subject,recipients=[to],body=body)mail.send(message)

假设我们程序是一个周刊订阅程序,当用户在表单中填写了正确的Email地址时,我们就发送一封邮件来通知用户订阅成功。通过在index视图中调用send_mail()即可发送邮件:

@app.route('/subscript',methods=['GET','POST'])
def subscript():form = SubscribeForm()if form.validate_on_submit():email = form.email.dataflash('Welcome on board!')send_mail('Subscribe Success!',email,'Hello,thank you for subscribing Flask Weekly!')return redirect(url_for('index'))return render_template('index.html',form=form)

二、使用事务邮件服务SendGrid

在生产环境中,除了自己安装运行邮件服务器外,更方便的做法是使用事务邮件服务,比如Mailgun、Sendgrid等。这两个邮件服务对免费账户分别提供每月1万封和3000封的免费额度,完全足够在测试使用或在小型程序中使用。Mailgun在注册免费账户时需要填写信用卡,而Sendgrid没有这一限制。

2.1、注册SendGrid

登录官网注册一个免费账户,访问https://app.sendgrid.com/signup,填写信息等完成注册。注册完成后,需要为当前的项目创建一个API密钥,用于在程序中发送邮件时进行认证。

创建成功后复制密钥,然后保存到.env文件中,待会使用它来作为发信账户的密码:

SENDRID_API_KEY=your_key_here

(API密钥被创建一次后仅显示一次,一旦关闭了显示界面,将无法再次查看。)

2.2、SendGrid SMTP转发

创建好API密钥后,就可以通过SendGrid提供的SMTP服务器发送电子邮件了。

MAIL_SERVER = 'smtp.sendgrid.net'
MAIL_PORT = 587
MAIL_USE_TLS = True
MAIL_USERNAME = 'apikey'
MAIL_PASSWORD = os.getenv('SENDGRID_API_KEY')	# 从环境变量中读取API密钥

2.3、SendGrid Web API转发

在程序中向SendGrid提供的Web API发出一个POST请求,并附带必要的信息,比如密钥、邮件主题、收件人、正文等。示例:

POST https://api.sendgrid.com/v3/mail/send
'Authorization: Bearer YOUR_API_KEY'
'Content-Type: application/json''{"personalizations":[{"to":[{"email":"zorn@example.com"}]}],"from":{"email":"noreply@helloflask.com"},"subject":"Hello,World!","content":[{"type":"text/plain","value":"Across the Great Wall we can reach every corner in the World."}]}'

在命令行中使用curl一类的工具,或是使用任意一个用于请求的Python库即可发送电子邮件,比如requests。为了更方便在Python中构建邮件内容和发送邮件,我们可以使用SendGrid提供的官方Python SDK---SendGrid-Python:

pip install sendgrid

2.3.1、创建发信对象

首先需要实例化SendGridAPIClient类创建一个发信客户端对象:

>>> from sendgrid import SendGridAPIClient
>>> sg = SendGridAPIClient(api_key=os.getenv('SENDGRID_API_KEY'))

2.3.2、构建邮件数据

from sendgrid.helpers.mail import Email,Content,Mail
from_email = Email('norn@helloflask.com')
to_email = Email('zorn@example.com')
subject = 'Hello World!'
content = Content('text/plain','Across the Great Wall we can reach every corner in the World.')
mail = Mail(from_email,subject,to_email,content)

2.3.3、发送邮件

通过对表示邮件对象的sg对象调用sg.client.mail.send.post()方法,并将表示数据的字典使用关键字request_body传入即可发送发信的POST请求:

sg.client.mail.send.post(request_body=mail.get())

发信的方法会返回响应,我们可以查看响应的内容:

response = sg.client.mail.send.post(request_body=mail.get())
print(response.status_code)
print(response.body)
print(response.headers)

同样的,可以创建一个发信函数用来在视图函数中调用:

import sendgrid
import os
from sendgrid.helpers.mail import *def send_email(subject,to,body):sg = SendGridAPIClient(api_key=os.getenv('SENDGRID_API_KEY'))from_email = Email('norn@helloflask.com')to_email = Email(to)content = Content('text/plain',body)mail = Mail(from_email,subject,to_email,content)response = sg.client.mail.send.post(request_body=mail.get())

三、电子邮件进阶

3.1、提供HRML正文

一封电子邮件的正文可以是纯文本(text/plain),也可以是HTML格式的文本(text/html)。出于更安全的考虑,一封电子邮件应该既包含纯文本又包含HTML格式的正文。HTML格式正文将被优先读取;对于HTML邮件正文的编写:

  • 使用Tabel布局,而不是Div布局
  • 使用行内(inline)样式定义,比如:
<span style="font-family:Arial,Helvetica,sans-serif;font-size:12px;color:##000000;">Hello Email!</span>
  • 使用比较基础的CSS属性,避免使用快捷属性(比如background)和定位属性(比如float、position)
  • 邮件正文的宽度不超过600px
  • 避免使用JavaScript代码
  • 避免使用背景图片

在Flask-Mail中,我们使用Message类实例来构建邮件。和纯文本正文类似,HTML正文可以在实例化时传入html参数指定:

message = Message(...,body='纯文本正文',html='<h1>HTML正文</h1>')

或是通过类属性message.html指定:

message = Message(...)
message.body = '纯正文文本'
message.html = '<h1>HTML文本</h1>'

在SendGrid-Python中,使用Content类构建邮件正文时传入的第一个type_ 参数指定了邮件正文的MIME类型。若想同时提供两种格式的正文,那么就在使用Mail类构建邮件数据时传入一个包含两个Content类实例的列表作为正文content的参数值:

from sendgrid.helpers.mail import *
...
text_content = Content("text/plain","纯正文文本")
html_content = Content("text/html","<h1>HTML文本</h1>")
mail = Mail(from_email,subject,to_email,content=[text=content,html_content])

3.2、使用Jinja2模板组织邮件正文

大多数情况下,我们需要动态构建邮件正文。示例一个纯文本邮件模板subscribe.txt:

Hello {{ name }},
Thank you for subscribing Flask weekly!
Enjoy the reading :)Visit this link to unsubscribe: {{ url_for('unsubscribe',_external=True) }}

为了同时支持纯文本和HTML格式的邮件正文,每一类邮件我们都需要分别创建HTML和纯文本格式的模板。

<div style="width: 580px; padding: 20px;"><h3>Hello {{ name }}</h3><p>Thank you for subscribing Flask Weekly!</p><p>Enjoy the reading :)</p><smail style="color: #868e96;">Click here to <a href="{{ url_for('unsubscribe',_external=True) }}">unsubscribe</a>.</smail>
</div>

以上面创建的发信函数为例,在发送邮件的视图函数中使用render_template()函数渲染邮件正文,并传入相应的变量:

from flask import render_template
from flask_mail import Messagedef send_subscribe_mail(subject,to,**kwargs):message = Message(subject,recipients=[to],sender='Flask Weekly <%s>' %os.getenv('MAIL_USERNAME'))message.body = render_template('emails/subscribe.txt',**kwargs)message.html = render_template('emails/subscribe.html',**kwargs)mail.send(message)

3.3、异步发送邮件

为了避免延迟,我们可以将发信函数放入后台线程异步执行,以Flask-Mail为例:

from threading import Threaddef _send_async_mail(app,message):with app.app_context():mail.send(message)def send_mail(subject,to,body):message = Message(subject,recipients=[to],body=body)thr = Thread(target=_send_async_mail,args=[app,message])thr.start()return thr

因为Flask_Mail的send()方法内部的调用逻辑中使用了current_app变量,而这个变量只在激活的程序上下文中才存在,这里在后台线程调用发信函数,但是后台线程并没有程序上下文存在。为了正常实现发信功能,我们传入程序实例app作为参数,并调用app,app_context()手动激活程序上下文

致谢

在此,我要对所有为知识共享做出贡献的个人和机构表示最深切的感谢。同时也感谢每一位花时间阅读这篇文章的读者,如果文章中有任何错误,欢迎留言指正。 

学习永无止境,让我们共同进步!!

相关文章:

Flask之电子邮件

前言&#xff1a;本博客仅作记录学习使用&#xff0c;部分图片出自网络&#xff0c;如有侵犯您的权益&#xff0c;请联系删除 目录 一、使用Flask-Mail发送电子邮件 1.1、配置Flask-Mail 1.2、构建邮件数据 1.3、发送邮件 二、使用事务邮件服务SendGrid 2.1、注册SendGr…...

Vue 2 与 ECharts:结合使用实现动态数据可视化

在现代前端开发中&#xff0c;数据可视化变得越来越重要。ECharts 是一个强大的数据可视化库&#xff0c;而 Vue 2 则是一个流行的前端框架。本文将介绍如何将 Vue 2 和 ECharts 结合使用&#xff0c;以实现动态数据可视化。 安装与配置 首先&#xff0c;确保你的项目中已经安…...

.net core Redis 使用有序集合实现延迟队列

Redis 有序集合和集合一样也是 string 类型元素的集合,且不允许重复的成员。 不同的是每个元素都会关联一个 double 类型的分数。redis 正是通过分数来为集合中的成员进行从小到大的排序。 有序集合的成员是唯一的,但分数(score)却可以重复。 集合是通过哈希表实现的&#xf…...

linux 安装Openjdk1.8

一、在线安装 1、更新软件包 sudo apt-get update 2、安装openjdk sudo apt-get install openjdk-8-jdk 3、配置openjdk1.8 openjdk默认会安装在/usr/lib/jvm/java-8-openjdk-amd64 vim ~/.bashrc export JAVA_HOME/usr/lib/jvm/java-8-openjdk-amd64 export JRE_HOME${J…...

鸿蒙系统:未来智能生态的引领者

在当今这个日新月异的互联网领域&#xff0c;操作系统作为连接硬件与软件的桥梁&#xff0c;其重要性不言而喻。随着华为鸿蒙系统&#xff08;HarmonyOS&#xff09;的崛起&#xff0c;一场关于操作系统未来的讨论再次被推向高潮。 鸿蒙OS&#xff0c;华为的全新力作&#xff…...

Java语言程序设计——篇二(1)

Java语言基础 数据类型关键字与标识符关键字标识符 常量与变量1、常量2、变量 类型转换自动类型转换强制类型转换 数据类型 数据的基本要素数据的性质&#xff08;数据结构&#xff09;数据的取值范围&#xff08;字节大小&#xff09;数据的存储方式参与的运算 Java是一门强类…...

水果商城系统 SpringBoot+Vue

1、技术栈 技术栈&#xff1a;SpringBootVueMybatis等使用环境&#xff1a;Windows10 谷歌浏览器开发环境&#xff1a;jdk1.8 Maven mysql Idea 数据库仅供学习参考 【已经答辩过的毕业设计】 项目源码地址 2、功能划分 3、效果演示...

半导体制造企业 文件共享存储应用

用户背景&#xff1a;半导体设备&#xff08;上海&#xff09;股份有限公司是一家以中国为基地、面向全球的微观加工高端设备公司&#xff0c;为集成电路和泛半导体行业提供具竞争力的高端设备和高质量的服务。 挑战&#xff1a;芯片的行业在国内迅猛发展&#xff0c;用户在上海…...

深入分析 Android BroadcastReceiver (九)

文章目录 深入分析 Android BroadcastReceiver (九)1. Android 广播机制的扩展应用与高级优化1.1 广播机制的扩展应用1.1.1 示例&#xff1a;有序广播1.1.2 示例&#xff1a;粘性广播1.1.3 示例&#xff1a;局部广播 1.2 广播机制的高级优化1.2.1 示例&#xff1a;使用 Pending…...

从数据到洞察:DataOps加速AI模型开发的秘密实践大公开!

作者 | 代立冬&#xff0c;白鲸开源科技联合创始人&CTO 引言 在AI驱动的商业世界中&#xff0c;DataOps作为连接数据与洞察的桥梁&#xff0c;正迅速成为企业数据战略的核心。 在WOT全球技术创新大会2024北京站&#xff0c;白鲸开源联合创始人&CTO 代立冬 在「大数据…...

全景图三维3D模型VR全景上传展示H5开发

全景图三维3D模型VR全景上传展示H5开发 3D互动体验平台的核心功能概览 兼容广泛格式&#xff1a;支持OBJ、FBX、GLTF等主流及前沿3D模型格式的无缝上传与展示&#xff0c;确保创意无界。 动态交互探索&#xff1a;用户可自由旋转、缩放、平移模型&#xff0c;深度挖掘每一处…...

前端面试题29(js闭包和主要用途)

JavaScript 中的闭包是一个非常强大的特性&#xff0c;它允许一个函数访问并操作其词法作用域之外的变量。闭包的形成主要依赖于函数的作用域链&#xff0c;即函数可以访问在其外部定义的变量&#xff0c;即使外部函数已经执行完毕。下面我会通过几个方面来帮助你理解闭包的概念…...

使用Keil 点亮LED灯 F103ZET6

1.新建项目 不截图了 2.startup_stm32f10x_hd.s Keil\Packs\Keil\STM32F1xx_DFP\2.2.0\Device\Source\ARM 搜索startup_stm32f10x_hd.s 复制到项目路径&#xff0c;双击Source Group 1 3.项目文件夹新建stm32f10x.h&#xff0c; 新建文件main.c #include "stm32f10x…...

流批一体计算引擎-12-[Flink]旁路输出getSideOutput(OutputTag)实现拆分流和复制流

官网旁路输出 Flink拆分流和复制流 我们在处理数据的时候,有时候想对不同情况的数据进行不同的处理,那么就需要把流进行拆分或者复制。 如果是使用filter来进行拆分,也能满足我们的需求,但每次筛选都要保留整个流,然后遍历整个流,显然很浪费性能,假如能够在一个流了多次…...

【Scrapy】 Scrapy 爬虫框架

准我快乐地重饰演某段美丽故事主人 饰演你旧年共寻梦的恋人 再去做没流着情泪的伊人 假装再有从前演过的戏份 重饰演某段美丽故事主人 饰演你旧年共寻梦的恋人 你纵是未明白仍夜深一人 穿起你那无言毛衣当跟你接近 &#x1f3b5; 陈慧娴《傻女》 Scrapy 是…...

【笔记】太久不用redis忘记怎么后台登陆了

&#xff01;首先启动虚拟机linux的centos7 2.启动finalshell 我的redis启动在根目录用 redis-server redis.conf --启动 systemctl status redis --查看redis状态 是否active redis-cli -h centos的ip地址 -p 你要用的redis端口号&#xff08;默认为6379&#xff09; -a 你…...

昇思25天打卡营-mindspore-ML- Day14-VisionTransformer图像分类

今天学习了Vision Transformer图像分类&#xff0c;这是一种基于Transformer模型的图像分类方法&#xff0c;它不依赖卷积操作&#xff0c;而是通过自注意力机制捕捉图像块之间的空间关系&#xff0c;从而实现图像分类。 基本原理&#xff1a; 图像分块: 将原始图像划分为多个…...

微信环境内H5网页,用开放标签wx-open-launch-app打开app

一、微信公众号后台配置安全域名 准备一个认证通过的公众号&#xff0c;打开公众号后台 1、设置与开发 2、公众号设置 3、功能设置 4、配置js接口安全域名 二、微信开放平台&#xff0c;将公众号与APP关联 打开微信开放平台后台 1、管理中心 2、公众号 3、选择一个需要操作…...

【c++基础】高精度数不进位加法

高精度数不进位加法 谈及数字即可想到运算&#xff0c;那么高精度数怎么运算呢&#xff1f;今天来系统介绍一下高精度数的加法。 思考一下加法运算&#xff0c;我们可以简单将加法运算这样区分&#xff1a; 有无进位。位数是否相同。 这篇文章我们就来讨论一下无进位的高精度…...

UniApp 中 Web/H5 正确使用反向代理解决跨域问题

因为 Vue3 的构建工具是 Vite&#xff0c;所以配置 vue.config.js 是没用的&#xff08;Vue2 因为使用 webpack 所以才用这个文件&#xff09; 这里提供一份 vue.config.js 的示例&#xff1a; module.exports {devServer: {proxy: {/api: {target: http://example.com,chan…...

Redis Stream:实时数据流的处理与存储

Redis Stream:实时数据流的处理与存储 引言 在当今数据驱动的世界中,实时数据处理和存储成为了许多应用的核心需求。Redis Stream作为一种新兴的数据结构,为Redis带来了强大的流处理能力。本文将深入探讨Redis Stream的特点、使用场景以及如何高效地利用它来处理实时数据流…...

【论文阅读】-- Visual Traffic Jam Analysis Based on Trajectory Data

基于轨迹数据的可视化交通拥堵分析 摘要1 引言2 相关工作2.1 交通事件检测2.2 交通可视化2.3 传播图可视化 3 概述3.1 设计要求3.2 输入数据说明3.3 交通拥堵数据模型3.4 工作流程 4 预处理4.1 路网处理4.2 GPS数据清理4.3 地图匹配4.4 道路速度计算4.5 交通拥堵检测4.6 传播图…...

修改编译依赖openssl的libcrypto.so

由于centos7默认使用openssl1.0.2k的libcrypto.so.10共享库。即使openssl升级为3.0.11后&#xff0c;编译使用ldd命令查看共享库依旧会引用libcrypto.so.10。 现希望引用libcrypto.so.3&#xff0c;需要在生成动态链接库的CMakeLists.txt中增加如下配置&#xff0c;明确指定ope…...

����: �Ҳ������޷��������� javafx.fxml ԭ��: java.lang.ClassNotFoundException解决方法

如果你出现了这个问题&#xff0c;恭喜你&#xff0c;你应该会花很多时间去找解决方法。别问我怎么知道的... 解决方法&#xff1a; 出现乱码的原因&#xff1a;配置vm时 这些配置看似由有空格&#xff0c;换行&#xff0c;实则没有。所以解决办法就是&#xff0c;重新配置你…...

【C++】———— 继承

作者主页&#xff1a; 作者主页 本篇博客专栏&#xff1a;C 创作时间 &#xff1a;2024年7月5日 一、什么是继承&#xff1f; 继承的概念 定义&#xff1a; 继承机制就是面向对象设计中使代码可以复用的重要手段&#xff0c;它允许在程序员保持原有类特性的基础上进行扩展…...

Python人生重开器

Life reopens stimulator """ 作者:->yjy 所有的惊艳都曾历经平庸 """ import random import sys import time# 打印初始界面 print(------------------------------) print(| |) print(| >>人生重…...

python 高级技巧 0708

python 33个高级用法技巧 使用装饰器计时函数 装饰器是一种允许在一个函数或方法调用前后运行额外代码的结构。 import timedef timer(func):"""装饰器函数&#xff0c;用于计算函数执行时间并打印。参数:func (function): 被装饰的函数返回:function: 包装后…...

HOW - React Router v6.x Feature 实践(react-router-dom)

目录 基本特性ranked routes matchingactive linksNavLinkuseMatch relative links1. 相对路径的使用2. 嵌套路由的增强行为3. 优势和注意事项4. . 和 ..5. 总结 data loadingloading or changing data and redirectpending navigation uiskeleton ui with suspensedata mutati…...

`padding`、`border`、`width`、`height` 和 `display` 这些 CSS 属性的作用

盒模型中的属性 padding&#xff08;内边距&#xff09; padding 用于控制元素内容与边框之间的空间&#xff0c;可以为元素的每个边&#xff08;上、右、下、左&#xff09;分别设置内边距。内边距的单位可以是像素&#xff08;px&#xff09;、百分比&#xff08;%&#xf…...

C++ QT 全局信号的实现

每次做全局信号都需要重新建立文件&#xff0c;太麻烦了&#xff0c;记录一下&#xff0c;以后直接复制。 头文件 globalSignalEmitter.h #pragma once //#ifndef GLOBALSIGNALEITTER_H //#define GLOBALSIGNALEITTER_H#include <QObject>class GlobalSignalEmitter : …...