Chemmy's Blog

chengming0916@outlook.com

名词解释

CA(Certificate Authority)

证书授权机构, 负责发放和管理数字证书的权威机构,有自己的证书,可以拿自己的证书给别人签名。

RootCA

根证书,权威机构持有的证书,安装根证书意味着对这个证书机构的信任,所有其他证书都由这个根证书来签发。只需要把这个根证书添加到受信任的根证书,所有其他由此根证书签发的证书都会被自动信任。

SubCA

中间证书机构,由权威机构签发的证书,

CSR(Certificate Signing Request)

证书请求文件,证书申请者在申请数字证书时生成私钥的同时也生成证书请求文件,证书申请者只要把CSR文件提交给证书颁发机构后,证书颁发机构使用其根证书给私钥签名就生成了证书公钥文件,也就是颁发给用户的证书。

常用后缀名
格式 说明
.crt,.cer 证书文件
.key 私钥文件
.csr 证书签名请求文件
.pem base64编码证书文件,可以单独放证书或密钥,也可以同时放两个。Apache 和 NGINX 服务器偏向于使用这种编码格式,也是 openssl 默认采用的信息存放方式。
.der 二进制证书文件,可包含所有私钥、公钥和证书,是大多数浏览器的缺省格式,常见于 Windows 系统中的证书格式。

证书链

在证书链中,通常分为三级结构,分别是根证书、中间证书、服务器证书。正确的证书链顺序中服务器证书处在最底端,里面包含服务器域名域名服务器公钥和签名值等。服务器证书的上一级是中间证书,可以由多张证书组合在一起,最上级是根证书。对服务器身份进行校验时,需要验证一整个证书链。每一级证书都有签名值,根证书使用自己的根CA公钥验证自己的签名,也用来验证中间证书的签名值,中间证书的公钥用来验证下一级证书的签名值。

生成根证书

生成根私钥
1
2
3
4
5
6
7
# 生成CA认证机构的证书密钥
# 需要设置密码(输入两次)
openssl genrsa -des3 -out root-ca.priv.key 4096

# 去除密钥里的密码,有密码的话每次使用的时候都要输入密码才能使用。
# 需要再输入一次上一步的密码
openssl rsa -in root-ca.priv.key -out root-ca.key

第一次生成的私钥,是带有 passphrase 的。这带来一个副作用,就是需要在使用过程中输入密码。这对于一些特定场景来说会带来一些问题。比如:Apache 的自动启动过程,或者一些工具,甚至有没有提供输入 passphrase 的机会。其实是可以将 3DES 的加密从秘钥中移除的,这样,使用的过程中就不再需要输入 passphrase。这也带来另一个问题,如果其他人获取到了未加密的私钥,对应的证书也需要被吊销,以避免带来危害。

生成自签名的根证书请求

-subj 参考证书请求文件参数说明

1
2
3
4
# 生成根证书自签名请求
openssl req -new -key root-ca.key \
-subj "/C=CN/ST=Tianjin/L=Tianjin/O=Example/OU=DEV/CN=Example Root" \
-out root-ca.csr
生成自签名根证书
1
2
3
4
# 参数说明
# -x509 使用X.509证书结构生成证书,X.509 证书的结构是用 ASN1(Abstract Syntax Notation One)进行描述数据结构。
# -days 证书有效期,按天来算
openssl x509 -req -in root-ca.csr -signkey root-ca.key -out root-ca.crt -days 3650

生成中间证书

如果不是复杂场景可以跳过此步骤,使用根证书直接生成客户端证书

使用root-ca签发sub-ca的证书签名请求,中间证书指的是可以允许继续生成下级证书,否则的话默认生成终端证书,即使可以用中间证书生成下一级客户端和服务端等用户证书,最终验证的无法通过。

生成中间证书私钥
1
2
3
4
5
6
# 生成私钥,方式1(参考根证书私钥)
openssl genrsa -des3 -out mid-ca.priv.key 4096
openssl rsa -in mid-ca.priv.key -out mid-ca.key

# 生成私钥,方式2
openssl genpkey -algorithm RSA -out mid-ca.key -pkeyopt rsa_keygen_bits:4096
生成中间证书请求

-subj参考证书请求文件参数说明

1
2
3
4
5
6
7
8
# 生成中间证书自签名请求
openssl req -new -key root-ca.key \
-subj "/C=CN/ST=Tianjin/L=Tianjin/O=Example/OU=DEV/CN=Example Root" \
-out mid-ca.csr

# 同时生成key和csr
openssl req -new -newkey rsa:4096 -nodes -keyout mid-ca.key -out mid-ca.csr \
-subj="/C=CN/ST=Tianjin/L=Tianjin/O=Example/OU=DEV/CN=Example Mid"
生成中间证书
1
2
3
4
# 生成证书
openssl x509 -req -extfile <(printf "subjectKeyIdentifier=hash\nauthorityKeyIdentifier=keyid:always,issuer:always") \
-days 3650 -in mid-ca.csr -CA root-ca.crt -CAkey root-ca.key \
-CAcreateserial -out mid-ca.crt
验证中间证书
1
openssl verify -CAfile root-ca.crt mid-ca.crt

生成终端证书

假设服务器域名为example.io

生成终端证书私钥
1
2
3
4
5
6
# 生成私钥,方式1(参考根证书私钥)
openssl genrsa -des3 -out example.io.priv.key 4096
openssl rsa -in example.io.priv.key -out example.io.priv.key

# 生成私钥,方式2
openssl genpkey -algorithm RSA -out example.io.key -pkeyopt rsa_keygen_bits:4096
生成终端证书请求

-subj 参考证书请求文件参数说明

1
2
# 生成终端证书自签名请求
openssl req -new -key example.io.key -out example.io.csr -subj="/CN=example.io"
生成终端证书
1
2
3
4
5
6
7
8
9
10
11
 # 生成证书,如果不使用中间证书可把mid-ca.crt替换为root-ca.crt
openssl x509 -req -days 3650 \
-extfile v3.ext
-CA root-ca.crt -CAkey root-ca.key -CAcreateserial \
-in example.io.csr -signkey example.io.key \
-out example.io.crt

openssl x509 -req -extfile v3.ext -days 365 -in example.io.csr -CA mid-ca.crt -CAkey mid-ca.key -CAcreateserial -out example.io.crt

# 导出pfx,
openssl pkcs12 -export -out example.io.pfx -inkey example.io.key -in example.io.crt

v3.ext参考X.509扩展配置

注意: v3.ext中basicConstraints=CA:FALSE为必选项,否则生成证书无法使用

验证终端证书
1
openssl verify -CAfile root-ca.crt example.io.crt

查看证书信息

1
2
3
4
5
6
7
8
9
10
11
# 查看公钥的内容,如果为.PEM ,则会以 base 64 明文方式显示
openssl rsa -noout -text -in cakey.key

# 查看证书的内容命令为
openssl x509 -noout -text -in cacert.crt

# 证书编码格式转换
# PEM 转为 DER
openssl x509 -in cacert.crt -outform der -out cacert.der
# DER 转为 PEM
openssl x509 -in cert.crt -inform der -outform pem -out cacert.pem

证书有效性验证

可以利用openssls_server命令来模拟一个服务端,要使用到证书管理员生成的证书client.crt,以及申请人在创建csr时生成的 client.key

1
openssl s_server -cert example.io.crt -key example.io.key -debug -HTTP -accept 443

然后浏览器访问 https://ip地址来查看证书是否有效(要先导入根证书到

信任的根证书颁发机构)。

吊销证书

一般由于用户私钥泄露等情况才需要吊销一个未过期的证书。

假设需要被吊销的证书文件为 cert.pem

1
2
3
4
5
6
7
8
# 生成证书吊销列表文件
# 可选参数
# -crldays 下一个吊销列表将在n天后发布
# -crlhours 下一个吊销列表将在n小时后发布
openssl ca -revoke cert.pem

# 查看吊销列表
openssl crl -in testca.crl -text -noout

附件

证书请求文件参数说明
参数 说明
/C Country Name (2 letter code) 两字母的国家代码,例如 “CN”。
/ST State or Province Name 州或省的全名。
/L Locality Name (e.g., city) 城市或地区的全名。
/O Organization Name (e.g., company) 公司或组织的全名。
/OU Organizational Unit Name (e.g., section) 部门或单位的全名。
/CN Common Name (e.g., your name or your server’s hostname) 通常是你的服务器的主机名。
emailAddress Email Address 电子邮件地址,用于证书联系。

这些信息将用于填写证书请求文件。在实际情况中,一些字段可能不是必需的,具体取决于你的使用场景和证书颁发机构(CA)的要求。通常,“Common Name” 是最重要的字段,应该设置为与你的服务器域名或主机名相匹配的值。其他字段的值可以根据实际情况填写。

示例

1
/C=CN/ST=Tianjin/L=Tianjin/O=Example/OU=DEV/CN=example.com/emailAddress=dev@example.com
X.509扩展配置

v3.ext

1
2
3
4
5
6
7
8
9
10
authorityKeyIdentifier=keyid,issuer
subjectKeyIdentifier=hash
basicConstraints=CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
#extendedKeyUsage = serverAuth
subjectAltName = @alt_names
[alt_names]
DNS.1=example.io
DNS.2=*.example.io
IP.3=192.168.0.2

extendedKeyUsage 可以指定证书目的,即用途,一般有

​ serverAuth 保证远程计算机的身份

​ clientAuth 向远程计算机证明你的身份

​ codeSigning 确保软件来自软件发布者,保护软件在发行后不被更改

​ emailProtection 保护电子邮件消息

​ timeStamping 允许用当前时间签名数据,如果不指定,则默认为 所有应用程序策略

SubjectAlternativeName

​ DNS.1用来确保网站的域名必须时*.example.com

​ IP.1用来确保网站的IP地址,如果证书里面的内容和实际对应不上,浏览器就会报错。

参考

构建安全的X.509三级证书体系:OpenSSL实战指南-百度开发者中心 (baidu.com)

如何创建自签名的 SSL 证书 - HappyVK - 博客园 (cnblogs.com)

关于OpeSSL生成自签名证书-包含完整证书链生成(全网最全) - 52只鱼 - 博客园 (cnblogs.com)

PKCS#12(.p12 或 .pfx)是一种常用的证书封装格式,通常包含私钥、公钥证书以及可选的CA证书链,并通过密码保护。以下是使用 OpenSSL 生成 PKCS#12 证书的完整流程。

步骤 1:生成私钥

1
openssl genpkey -algorithm RSA -out private.key -aes256

此命令生成一个 2048位RSA私钥 并使用 AES-256 加密,系统会提示输入保护私钥的密码。

步骤 2:生成证书签名请求(CSR)

1
openssl req -new -key private.key -out request.csr

根据提示填写国家、组织、域名等信息,这些将写入证书中。

步骤 3:生成自签名证书

1
2
openssl x509 -req -days 365 -in request.csr \ 
-signkey private.key -out certificate.crt

该命令生成一个有效期为 365天 的自签名 X.509 证书。

步骤 4:导出 PKCS#12 文件

1
2
3
4
5
openssl pkcs12 -export \
-in certificate.crt \
-inkey private.key \
-out certificate.p12 \
-name "MyCert"

执行时会提示输入导出密码,用于保护 .p12 文件中的私钥。

步骤 5:验证 PKCS#12 文件

1
openssl pkcs12 -info -in certificate.p12

可查看 .p12 文件中包含的证书和私钥信息。

最佳实践与提示:

若需包含 CA 链,可在导出时添加 -chain -CAfile ca.crt 参数。

私钥建议使用强加密算法(如 AES-256)并妥善保存。

.p12 文件常用于 HTTPS服务器部署、客户端身份认证 或 代码签名 场景。

这样即可从零生成一个安全的 PKCS#12 格式证书,并可直接用于 Nginx、Tomcat、Java Keystore 等环境。

一、项目概述

随着 DeepSeek 等 AI 大模型的普及,我们有了更多有趣的玩法。本文将介绍如何利用 Python 脚本,结合 AI 大模型(以 DeepSeek 为例)和微信公众号 API,实现从文章创作到发布的全自动化流程

核心流程

  1. AI 创作:调用 DeepSeek API,根据热点或指定主题生成文章。
  2. 公众号对接:通过微信公众号 API,获取凭证、上传素材并发布文章。
  3. 自动化整合:将以上步骤串联,实现一键生成并发布。

二、第一步:接入 AI 创作引擎(DeepSeek)

2.1 获取 DeepSeek API Key

  1. 访问 DeepSeek 官方平台
  2. 登录后,进入 API 管理页面,创建一个新的 API Key 并妥善保存。

2.2 创建调用 DeepSeek API 的函数

以下 Python 函数 get_article_from_deepseek 用于向 DeepSeek 发送请求并获取生成的文本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import requests

# DeepSeek API 配置
DEEPSEEK_API_URL = "https://api.deepseek.com/v1/chat/completions"
DEEPSEEK_API_KEY = "sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" # 替换为你的 API Key

def get_article_from_deepseek(my_describe):
"""
调用 DeepSeek API 生成文章。
:param my_describe: 给 AI 的提示词,描述文章要求。
:return: 生成的文本内容,失败则返回 None。
"""
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {DEEPSEEK_API_KEY}"
}
data = {
"model": "deepseek-chat", # 指定模型
"messages": [
{
"role": "user",
"content": my_describe
}
]
}
try:
response = requests.post(DEEPSEEK_API_URL, headers=headers, json=data)
if response.status_code == 200:
result = response.json()
article = result["choices"][0]["message"]["content"]
return article
else:
print(f"DeepSeek 请求失败,状态码:{response.status_code},错误信息:{response.text}")
except requests.RequestException as e:
print(f"DeepSeek 请求发生错误:{e}")
return None

2.3 测试 AI 创作

调用函数,让 AI 根据热点生成一篇公众号风格的文章。

1
2
3
4
5
6
7
8
9
10
11
12
13
# 示例:让 AI 基于近期热点创作
prompt = """
请查询最近三天内的热搜榜,取其中的一个热点,生成一篇公众号文章。
要求:
1. 标题要吸引人,有争议性。
2. 内容完整,结构清晰(有引言、主体、结论)。
3. 不能涉及侵权行为。
4. 输出纯文本,无需 Markdown 格式。
"""
article_content = get_article_from_deepseek(prompt)
if article_content:
print("文章生成成功!")
print(article_content[:500]) # 预览前500字符

效果与成本

  • AI 能快速生成一篇结构完整、风格贴近公众号的文章。
  • 一篇短文的生成成本(输入+输出 tokens)通常不足 1000 tokens,花费极低。


三、第二步:接入微信公众号 API

3.1 获取公众号凭证

  1. 登录 微信公众平台
  2. 进入 开发 -> 基本配置,获取 AppIDAppSecret

    新手建议:首次测试可使用 “开发者工具” 中的 “公众平台测试账号”,功能齐全且无风险。

3.2 创建公众号 API 操作函数

我们需要四个核心函数来处理公众号的访问令牌、素材上传和文章发布。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# 微信公众号配置
WECHAT_APPID = "你的AppID"
WECHAT_APPSECRET = "你的AppSecret"
WECHAT_API_URL = "https://api.weixin.qq.com"

def get_wechat_access_token():
"""获取微信公众号的 access_token。"""
url = f"{WECHAT_API_URL}/cgi-bin/token?grant_type=client_credential&appid={WECHAT_APPID}&secret={WECHAT_APPSECRET}"
try:
response = requests.get(url)
if response.status_code == 200:
result = response.json()
if 'access_token' in result:
return result['access_token']
else:
print(f"获取 access_token 失败,错误信息:{result}")
except requests.RequestException as e:
print(f"获取 access_token 发生错误:{e}")
return None

def upload_cover_image(access_token, image_path):
"""上传封面图片并获取 media_id。"""
url = f"{WECHAT_API_URL}/cgi-bin/media/upload?access_token={access_token}&type=image"
with open(image_path, 'rb') as file:
files = {'media': file}
try:
response = requests.post(url, files=files)
if response.status_code == 200:
result = response.json()
if 'media_id' in result:
return result['media_id']
else:
print(f"上传封面图片失败,错误信息:{result}")
except requests.RequestException as e:
print(f"上传封面图片发生错误:{e}")
return None

def create_article_material(access_token, article, thumb_media_id):
"""将文章内容创建为公众号素材,并返回素材 media_id。"""
url = f"{WECHAT_API_URL}/cgi-bin/media/uploadnews?access_token={access_token}"
# 注意:公众号文章内容需要是 HTML 格式
data = {
"articles": [
{
"title": "AI生成的热点文章", # 文章标题
"thumb_media_id": thumb_media_id, # 封面图片素材 ID
"author": "AI Writer", # 作者
"digest": article[:100] + "...", # 文章摘要(取前100字符)
"show_cover_pic": 0, # 是否在正文显示封面图片
"content": article,
"content_source_url": "" # 原文链接(可留空)
}
]
}
try:
response = requests.post(url, json=data)
if response.status_code == 200:
result = response.json()
if 'media_id' in result:
return result['media_id']
else:
print(f"创建文章素材失败,错误信息:{result}")
except requests.RequestException as e:
print(f"创建文章素材发生错误:{e}")
return None

def publish_article(access_token, media_id):
"""将已创建的素材发布给所有粉丝。"""
url = f"{WECHAT_API_URL}/cgi-bin/message/mass/sendall?access_token={access_token}"
data = {
"filter": {
"is_to_all": True # 发送给所有用户
},
"mpnews": {
"media_id": media_id
},
"msgtype": "mpnews"
}
try:
response = requests.post(url, json=data)
if response.status_code == 200:
result = response.json()
if result.get('errcode') == 0:
print("文章发布成功!")
else:
print(f"文章发布失败,错误信息:{result}")
except requests.RequestException as e:
print(f"文章发布发生错误:{e}")

四、第三步:组装自动化“工厂”

4.1 创建主函数串联流程

将 AI 创作和公众号发布流程整合到一个主函数中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
import os

def main():
"""主函数:一键生成并发布文章。"""
print("开始自动创作与发布流程...")

# 1. AI 生成文章
prompt = "请以‘职场人的下班自由’为主题,写一篇800字左右的公众号文章,要求观点鲜明,引发共鸣。"
print("正在调用 AI 生成文章...")
article_content = get_article_from_deepseek(prompt)

if not article_content:
print("文章生成失败,流程终止。")
return

# 2. 处理编码与格式(防止乱码)
# 公众号 API 要求内容为 UTF-8 编码的字符串
article_content = article_content.encode('utf-8').decode('utf-8')
# 可选:将纯文本转换为简单的 HTML 段落格式,以提升公众号阅读体验
html_content = "<p>" + article_content.replace("\n", "</p><p>") + "</p>"

# 3. 获取公众号访问令牌
print("正在获取微信公众号访问令牌...")
access_token = get_wechat_access_token()
if not access_token:
print("获取访问令牌失败,流程终止。")
return

# 4. 上传封面图片(需提前准备一张 cover.jpg)
cover_image_path = "./cover.jpg"
if not os.path.exists(cover_image_path):
print(f"封面图片不存在:{cover_image_path},请准备一张封面图。")
# 此处可以改为使用默认图或跳过
return
print("正在上传封面图片...")
thumb_media_id = upload_cover_image(access_token, cover_image_path)
if not thumb_media_id:
print("上传封面图片失败,流程终止。")
return

# 5. 创建文章素材
print("正在创建公众号文章素材...")
media_id = create_article_material(access_token, html_content, thumb_media_id)
if not media_id:
print("创建文章素材失败,流程终止。")
return

# 6. 发布文章
print("正在发布文章...")
publish_article(access_token, media_id)

print("自动化流程执行完毕!")

if __name__ == "__main__":
main()

4.2 运行与测试

  1. 确保已安装 requests 库:pip install requests
  2. 将代码中的 DEEPSEEK_API_KEYWECHAT_APPIDWECHAT_APPSECRET 替换为你自己的凭证。
  3. 准备一张名为 cover.jpg 的封面图片,放在脚本同目录下。
  4. 运行脚本 python auto_publish.py

成功提示:如果一切顺利,你将在公众号后台的“素材管理”中看到新创建的文章,并且所有粉丝都会收到推送。


五、进阶优化与展望

5.1 当前流程优化

  • 内容格式化:在提示词中要求 AI 直接输出带 HTML 标签的格式,减少后期处理。
  • 错误重试:为网络请求添加重试机制,提高稳定性。
  • 日志记录:将运行状态、生成的文章内容、API 响应等记录到文件,便于排查。
  • 多热点源:结合其他 API(如微博、百度热搜)获取实时热点,作为 AI 的创作素材。

5.2 扩展为“AI 运营管家”

设想一个更强大的自动化系统:

  1. GUI 管理界面:使用 PyQtTkinter 制作可视化控制面板。
  2. 任务调度:使用 APScheduler 库,实现定时任务(如每小时抓取热点,生成文章存入素材池)。
  3. 账号矩阵管理:管理多个公众号,根据账号定位(美食、情感、科技)自动匹配不同风格的文章并发布。
  4. 数据分析与反馈:调用公众号数据接口,分析文章阅读量、点赞数,用 AI 总结规律,并自动调整后续的创作策略(如标题风格、发文时间)。
  5. 全自动化部署:将整套脚本部署到云服务器(如腾讯云、阿里云),实现 7x24 小时无人值守运营。

最终愿景:实现“一人运营百号”,运营者只需定期查看数据报告,与 AI 讨论策略,将重复性工作完全自动化。


六、注意事项

  1. 内容审核:AI 生成的内容需符合平台规范,发布前建议人工审核,避免违规风险。
  2. API 限制:关注 DeepSeek 和微信公众号的 API 调用频率、配额限制。
  3. 版权与伦理:确保生成的内容不侵犯他人版权,注明 AI 辅助创作。
  4. 凭证安全:切勿将 API KeyAppSecret 等敏感信息提交到公开的代码仓库。

通过本文的指南,你可以快速搭建一个属于自己的“AI 小编”系统,探索内容创作自动化的无限可能。

方法1

最简单的一种方法,将当前用户添加到root组。这样做之后该用户将拥有所有root权限,从而使用户在使用sudo时不再需要输入密码。

1
sudo adduser {当前用户名} root 

注意: 将用户添加到root组后,该用户将拥有最高权限,因此在执行系统级操作时要特别小心,以避免可能的系统破坏。

方法2(推荐)

通过编辑/etc/sudoers文件来配置特定用户或组的sudo行为。这种方法允许更灵活的控制哪些命令可以绕过密码提示。
![[Linux配置sudo无密码/IMG-20250319160639846.png]]

单用户配置

1
{用户名} ALL=(ALL:ALL) NOPASSWD:ALL #在 root 后新添加一行

用户组配置

1
%{用户组} ALL=(ALL:ALL) NOPASSWD:ALL # 在 %sudo 后添加

使用此方式需将用户添加进组

1
sudo usermod -a -G {用户组} {用户名}

安装Golang

Windows版本下载安装程序安装即可,Ubuntu下有两种安装方式
第一种方式

1
2
sudo apt update
sudo apt install golang

此种方式安装的版本并不是最新版,或者下载 [官方包]执行

1
2
sudo tar -zxvf go1.24.2.linux-amd64.tar.gz -C  /usr/local # 解压缩到/usr/local
sudo chmod 755 -R /usr/local/go # 修改权限,一般解压后就有权限,此步可以省略

配置环境变量

Windows

打开 系统设置->关于->高级系统设置->环境变量,注意如果只给当前用户使用添加到用户的环境变量即可,全部用户可用需配置系统环境变量。

GOROOT

![[Golang环境配置/IMG-20250804110742705.png]]

GOPATH
![[Golang环境配置/IMG-20250804110742880.png]]

GOPROXY

![[Golang环境配置/IMG-20250804110743185.png]]

Ubuntu

如果只给当前用户使用编辑用户目录下.bashrc文件,全部用户可用需配置/etc/profile

1
2
3
export GOROOT=/usr/local/go # Golang根目录
export GOPATH=/home/go-project # 项目根目录
export PATH=$GOROOT/bin:$GOPATH/bin:$PATH

进阶配置

GOPROXY可在环境变量中配置也可使用go env -w命令配置

1
2
3
go env -w GOPROXY=https://goproxy.cn # 七牛
go env -w GOPROXY=https://mirrors.aliyun.com/goproxy/ # 阿里
go env -w GOPROXY=https://goproxy.io # 官方

GO111MODULE
从go 1.11版本开始,推荐使用Go Modules进行包管理。

1
go env -w GO111MODULE=on

在项目目录下初始化模块

1
go mod init 

1. 第一个Qt程序

1.1 Hello Qt

1
2
3
4
5
6
7
8
9
10
#include <QApplication>          // 头文件引用, 每个Qt类都有对应的头文件,类的定义
#include <QLabel> // 包含了对该类的定义

int main(int argc, char *argv[])
{
QApplication app(argc, argv); // 创建QApplication对象,管理整个应用
QLabel *label = new QLabel("Hello Qt"); // QLabel部件,用于显示
label->show(); // # 使QLabel部件可见
return app.exec(); // 将应用程序的控制权传递给Qt,程序等待用户操作
}

测试程序

图片占位

在源码根目录打开命令提示符执行qmake -project生成hello.pro项目文件,然后执行qmake hello.pro从这个项目文件生成makefile文件,在输入make命令就可以构建该应用。

方式一、虚拟ip访问

安装docker时,docker会默认创建一个内部的桥接网络docker0,每创建一个容器分配一个虚拟网卡,容器之间可以根据ip互相访问。

1
2
3
4
5
6
7
8
9
10
11
# ifconfig
---
docker0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 172.17.0.1 netmask 255.255.0.0 broadcast 0.0.0.0
inet6 fe80::42:35ff:feac:66d8 prefixlen 64 scopeid 0x20<link>
ether 02:42:35:ac:66:d8 txqueuelen 0 (Ethernet)
RX packets 4018 bytes 266467 (260.2 KiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 4226 bytes 33935667 (32.3 MiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
---

运行一个centos镜像, 查看ip地址得到:172.17.0.7

1
2
3
4
5
6
7
8
9
10
11
12
13
# docker run -it --name centos-1 docker.io/centos:latest
# ifconfig

---
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 172.17.0.7 netmask 255.255.0.0 broadcast 0.0.0.0
inet6 fe80::42:acff:fe11:7 prefixlen 64 scopeid 0x20<link>
ether 02:42:ac:11:00:07 txqueuelen 0 (Ethernet)
RX packets 16 bytes 1296 (1.2 KiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 8 bytes 648 (648.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
---

以同样的命令再起一个容器,查看ip地址得到:172.17.0.8

1
2
3
4
5
6
7
8
9
10
11
12
13
# docker run -it --name centos-2 docker.io/centos:latest
# ifconfig

---
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 172.17.0.8 netmask 255.255.0.0 broadcast 0.0.0.0
inet6 fe80::42:acff:fe11:8 prefixlen 64 scopeid 0x20<link>
ether 02:42:ac:11:00:08 txqueuelen 0 (Ethernet)
RX packets 8 bytes 648 (648.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 8 bytes 648 (648.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
---

容器内部ping测试结果如下:

1
2
3
4
5
6
7
8
9
# ping 172.17.0.7

---
PING 172.17.0.7 (172.17.0.7) 56(84) bytes of data.
64 bytes from 172.17.0.7: icmp_seq=1 ttl=64 time=0.205 ms
64 bytes from 172.17.0.7: icmp_seq=2 ttl=64 time=0.119 ms
64 bytes from 172.17.0.7: icmp_seq=3 ttl=64 time=0.118 ms
64 bytes from 172.17.0.7: icmp_seq=4 ttl=64 time=0.101 ms
---

这种方式必须知道每个容器的ip,在实际使用中并不实用。

运行容器的时候加上参数link

运行第一个容器

1
docker run -it --name centos-1 docker.io/centos:latest

运行第二个容器

1
2
3
docker run -it --name centos-2 \ 
--link centos-1:centos-1 \
docker.io/centos:latest

--link:参数中第一个centos-1是 容器名 ,第二个centos-1是定义的 容器别名 (使用别名访问容器),为了方便使用,一般别名默认容器名。

测试结果如下:

1
2
3
4
5
6
7
8
9
# ping centos-1

---
PING centos-1 (172.17.0.7) 56(84) bytes of data.
64 bytes from centos-1 (172.17.0.7): icmp_seq=1 ttl=64 time=0.210 ms
64 bytes from centos-1 (172.17.0.7): icmp_seq=2 ttl=64 time=0.116 ms
64 bytes from centos-1 (172.17.0.7): icmp_seq=3 ttl=64 time=0.112 ms
64 bytes from centos-1 (172.17.0.7): icmp_seq=4 ttl=64 time=0.114 ms
---

此方法对容器创建的顺序有要求,如果集群内部多个容器要互访,使用就不太方便。

方式三、创建bridge网络

  1. 安装好docker后,运行如下命令创建bridge网络:
1
docker network create testnet

查询到新创建的bridge testnet。

  1. 运行容器连接到testnet网络。
    使用方法:
    1
    2
    3
    docker run -it --name <容器名> \
    --network <bridge> \
    --network-alias <网络别名> <镜像名>
1
2
3
4
docker run -it --name centos-1 --network testnet \
--network-alias centos-1 docker.io/centos:latest
docker run -it --name centos-2 --network testnet \
--network-alias centos-2 docker.io/centos:latest
  1. 从一个容器ping另外一个容器,测试结果如下:
1
2
3
4
5
6
7
8
9
# ping centos-1

---
PING centos-1 (172.20.0.2) 56(84) bytes of data.
64 bytes from centos-1.testnet (172.20.0.2): icmp_seq=1 ttl=64 time=0.158 ms
64 bytes from centos-1.testnet (172.20.0.2): icmp_seq=2 ttl=64 time=0.108 ms
64 bytes from centos-1.testnet (172.20.0.2): icmp_seq=3 ttl=64 time=0.112 ms
64 bytes from centos-1.testnet (172.20.0.2): icmp_seq=4 ttl=64 time=0.113 ms
---
  1. 若访问容器中服务,可以使用这用方式访问 <网络别名>:<服务端口号>

推荐使用这种方法,自定义网络,因为使用的是网络别名,可以不用顾虑ip是否变动,只要连接到docker内部bright网络即可互访。bridge也可以建立多个,隔离在不同的网段。

准备

镜像选择

quay.io/coreos/etcd:3.2.7
bitname/etcd

创建ETCD数据目录

1
2
3
# data 存储容器持久化数据
# config 存储容器使用的配置文件
mkdir -p /usr/local/docker/etcd/{data,config}

创建ETCD配置文件

配置文件路径为 /usr/local/docker/etcd/config/etcd.config.yml

1
2
3
4
5
6
7
8
9
10
11
12
name: etcd # etcd member 名称,可根据实际情况修改
data-dir: /var/etcd #etcd 数据目录,可根据实际情况修改
listen-client-urls: http://0.0.0.0:2379 #client 流量监听地址,没特殊需求按文档填写即可
advertise-client-urls: http://0.0.0.0:2379 # 该 member 向外部通告的客户端 url 列表,单节点部署时不需要修改,集群部署模式需修改为容器所在节点对外提供服务的 IP
listen-peer-urls: http://0.0.0.0:2380 # peer 流量监听地址,没特殊需求按文档填写即可
initial-advertise-peer-urls: http://0.0.0.0:2380 # 该 member 向同一集群内其他 member 通告的 peer url 列表,单节点部署时不需要修改,集群部署模式需修改为容器所在节点对外提供服务的 IP
initial-cluster: etcd=http://0.0.0.0:2380 # 初始化集群节点信息,单节点部署时不需要修改,集群部署模式需要填写集群中所有 member 的信息
initial-cluster-token: etcd-cluster # 初始化集群时使用的 token,随便写
initial-cluster-state: new # 初始化集群状态,可选的值为 **new** 或者 **existing**,通常采用 **new**
logger: zap
log-level: info
# log-outputs: stderr

创建并启动ETCD服务

1
docker run -d --name etcd -p 2379:2379 -p 2380:2380 -v /usr/local/docker/etcd/data:/var/etcd -v /usr/local/docker/etcd/config:/var/lib/etcd/config quay.io/coreos/etcd: 3.5.12 

使用docker-compose部署

创建Docker-compose

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
version: '3'

services:
etcd:
container_name: etcd
image: quay.io/coreos/etcd:v3.5.12
command: /usr/local/bin/etcd --config-file=/var/lib/etcd/config/etcd.conf.yml
volumes:
- ${DOCKER_VOLUME_DIRECTORY:-.}/data:/var/lib/etcd
- ${DOCKER_VOLUME_DIRECTORY:-.}/config/etcd.config.yml:/var/lib/etcd/conf/etcd.conf.yml
ports:
- 2379:2379
- 2380:2380
restart: always

networks: # 船舰一个新的bridge模式网络,名称 etcd-tier,名称可以根据需求自定义
default:
name: etcd-tier
driver: bridge

基于环境变量配置, 配置参考配置文件方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
version: '3'

services:
etcd:
container_name: etcd
image: quay.io/coreos/etcd:v3.5.12
environment:
- ETCD_NAME=etcd
- ETCD_DATA_DIR=
- ETCD_LISTEN_CLIENT_URLS=
- ETCD_ADVERTISE_CLIENT_URLS=
- ETCD_LISTEN_PEER_URLS=
- ETCD_INSTALL_ADVERTISE_PEER_URLS=
- ETCD_INSTALL_CLUSTER_TOKEN=
- ETCD_INSTALL_CLUSTER
- ETCD_INSTALL_CLUSTER_STATE=new
- ETCD_LOGGER=zap
- ETCD_LOG_LEVEL=info
- ALLOW_NONE_AUTHENTICATION="yes" # 允许无身份验证访问
- TZ="Asia/Shanghai"
volumes:
- ${DOCKER_VOLUME_DIRECTORY:=.}/data:/var/etcd
- /etc/localtime:/etc/localtime:rw
ports:
- 2379:2379
- 2380:2380
restart: always
networks:
default:
name: etcd-tier
driver: bridge

创建并启动etcd

1
2
cd {docker-compose文件所在目录}
docker compose up -d

测试命令

1
2
3
4
5
6
7
8
9
10
11
12
etcdctl --endpoints=192.168.1.2:2379 --write-out=table endpoint health

etcdctl --endpoints=192.168.1.2:2379 --write-out=table endpoint status

# 查看member状态
etcdctl --endpoints=192.168.1.2:2379 --write-out=table member list

# 写入数据
etcdctl --endpoints=192.168.1.2:2379 put foo bar

# 读取数据
etcdctl --endpoints=192.168.1.2:2379 get foo

获取VideoCapture实例

1
2
3
4
5
6
7
8
# 读取视频流
strem_capture = cv2.VideoCapture("rtst://192.168.0.0/live/demo")

# 读取视频文件
file_capture = cv2.VideoCapture('demo.mp4')

# 读取摄像头
capture = cv2.VideoCapture(0)

获取摄像头编号可使用ls -al /dev/ |grep video,输出信息以video开头其后缀为数字即为可能的摄像头编号。

检查获取VideoCapture实例是否成功

1
2
3
# 校验获取VideoCapture类实例
if not capture.isOpened():
return

获取视频流信息

1
2
3
4
5
6
7
8
# 获取视频帧的宽
width = capture.get(cv2.CAP_PROP_FRAME_WIDTH)

# 获取视频帧的高
height = cpature.get(cv2.CAP_PROP_FRAME_HEIGHT)

# 获取视频帧率
fps = capture.get(cv2.CAP_PROP_FPS)

获取帧画面

1
success, frame = capture.read()

当需要同时处理多路摄像头时一般使用grab()retrieve()代替

1
2
3
4
5
6
7
success_1 = capture.grab()
success_2 = stream_capture.grab()

if success_1 and success_2:
frame_1 = capture.retrieve()
frame_2 = stream_capture.retrieve()

设置分辨率

1
2
3
4
5
# 设置摄像头分辨率的宽
capture.set(cv2.CAP_PROP_FRAME_WIDTH, 1920)

# 设置摄像头分辨率的高
capture.set(cv2.CAP_PROP_FRAME_HEIGHT, 1080)

保存视频文件

无论是视频文件存储还是摄像头画面保存都是用VideoWriter类,初始化时需要传入文件名(包含文件格式)、视频编解码器、视频保存帧率 、分辨率,保存视频的帧率最好和读入的帧率一致,分辨率可以更改,只是要求写入的帧大小要与分辨率保持一致。
若指定的文件名已存在则会覆盖文件。

1
2
3
writer = cv2.VideoWriter('output.mp4', 
cv2.VideoWriter_fourcc(*'MP4V'), 30, (1080,1920))
writer.write(frame)

释放资源

不管是VideoCapture还是VideoWriter类,使用完都应该释放资源

1
2
3
4
5
# 释放VideoCapture资源
capture.release()

# 释放VideoWriter资源
writer.release()

完整示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# -*- coding: utf-8 -*-
# /usr/bin/env/python3

import cv2
import time

capture = cv2.VideoCapture('rtsp://192.168.0.0/live/demo)
fourcc = cv2.VideoWriter_fourcc(*'MP$v') # 或H264,H265
fps = capture.get(cv2.CAP_PROP_FPS)
width = capture.get(cv2.CAP_PROP_FRAME_WIDTH)
height = capture.get(cv2.CAP_PROP_FRAME_HEIGHT)

writer = cv2.VideoWriter('demo.mp4', fourcd, fps, (height, width))


while True:
if not capture.isOpened():
time.sleep(0.5)
continue
success, frame = capture.read()
if success:
cv2.imshow('DEMO', frame) # 显示画面
writer.write(frame) # 保存视频文件
if (cv2.waitKey(20) & 0xff) == ord('q'): # 等待20ms并判断是否按下'q'退出,waitkey只能传入整数,
break

capture.release() # 释放VideoCapture
writer.release() # 释放VideoWriter
cv2.destroyAllWindows() # 销毁所有opencv显示窗口
0%