0%

宽松许可

  • MIT
  • Apache
  • BSD
    鼓励开源和代码共享,允许修改、再发布,尊重原作者权利,在发布或代码引用处声明BSD协议

弱化的著作权

  • LGPL

著作权

  • GPL

使用 jenkins ant 自动化脚本调用build.xml任务

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
<?xml version="1.0" ?>
<project basedir="." default="build_all" name="TestProj">
<description>
TestProj Build File.
</description>
<tstamp>
<format locale="en" pattern="yyMMdd" property="TODAY_CN"/>
</tstamp>

<property name="build_ver" Value="1.0.5.0" />

<target description="Create publish folder" name="create_folder">
<delete dir="${build_ver}" quiet="true"/>
<mkdir dir="${build_ver}\publish"/>
</target>

<target description="Pack TestProj" name="sign_and_copy">
<exec dir="." executable="cmd.exe">
<arg line="/c"/>
<arg line=".\uac_cert\signtool.exe sign /f ".\uac_cert\test.pfx" /p passwordxxx /fd SHA256 /t "http://timestamp.digicert.com" "..\TestProj\bin\Release\TestProj.exe""/>
</exec>

<copy file="..\TestProj\bin\Release\TestProj.exe" overwrite="true" todir="${build_ver}\publish"/>
<copy file="..\TestProj\bin\Release\Microsoft.Identity.Client.dll" overwrite="true" todir="${build_ver}\publish"/>
<copy file="..\TestProj\bin\Release\Newtonsoft.Json.dll" overwrite="true" todir="${build_ver}\publish"/>
<zip destfile="${build_ver}\publish.zip" basedir="${build_ver}\publish" />
<delete dir="${build_ver}\publish" quiet="true"/>
</target>

<!-- Build All -->
<target description="Build solution" name="build_all">
<antcall target="create_folder"/>
<antcall target="pack_pubclient"/>
</target>
</project>


其中使用signtool.exe给构建生成的应用添加数字签名,签名密钥保存在pfx文件中 需使用password访问

issue: capicom.dll没有正确安装或者是没有注册

加密API组件对象模型(Cryptographic API Component Object Model,capicom)微软Windows系统组件,用于以数字方式签署数据代码、验证数字签章、加密解密等,见CryptoAPI

注册capicom

1
Regsvr32 c:/windows/system32/capicom.dll

System.Diagnostics命名空间

提供允许你与系统进程、事件日志和性能计数器进行交互的类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
using System.Diagnostics
...
class Program
{
static void Main(string[] args){
InitLog()
Trace.WriteLine($"log is here")
}
private static void InitLog(){
string logDirectory = @"D:\log\"
string timestamp = DateTime.UtcNow.ToString("yyyyMMdd_HHmmss", CultureInfo.InvariantCulture);
logDirectory = $"{logDirectory}_{timestamp}.log";
Trace.Listeners.Add(new TextWriterTraceListener(File.CreateText(logDirectory)));
Trace.AutoFlush = true;
}
}

argparse模块是python用于解析命令行参数和选项的标准模块 对于封装好的py函数文件 可实现在命令行输出—help的效果:

定义函数文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#-*- coding: UTF-8 -*-
import argparse

def parse_args():
"""
:return:进行参数的解析
"""
description = "you should add those parameter"
parser = argparse.ArgumentParser(description=description) # 这些参数都有默认值,当调用parser.print_help()或者运行程序时由于参数不正确(此时python解释器其实也是调用了pring_help()方法)时,
# 会打印这些描述信息,一般只需要传递description参数,如上。
mode_desc = "action mode"
image_desc = "the path of image"
parser.add_argument('--mode', help=mode_desc)
parser.add_argument('--image', help=image_desc)
args = parser.parse_args()
return args

if __name__ == '__main__':
args = parse_args()
if()

命令行输入
1
python arg.py -h

输出提示为
1
2
3
4
5
6
7
8
9
10
Windows PowerShell
Copyright (C) Microsoft Corporation. All rights reserved.

you should add those parameter

optional arguments:
-h, --help show this help message and exit
--addresses ADDRESSES
The path of address

ArgumentParser.add_argument(name or flags…[, action][, nargs][, const][, default][, type][, choices][, required][, help][, metavar][, dest])

  • name or flags — 选项字符串的名字或者列表,例如 foo 或者 -f, —foo。
  • action— 命令行遇到参数时的动作,默认值是 store; store_const—表示赋值为const;append—将遇到的值存储成列表,也就是如果参数重复则会保存多个值; append_const—将参数规范中定义的一个值保存到一个列表;count—存储遇到的次数;此外,也可以继承 argparse.Action 自定义参数解析动作;

  • nargs — 应该读取的命令行参数个数,可以是数字,或者通配符如?表示0个或1个;*表示 0 或多个;+表示 1 或多个。

  • const - action 和 nargs 所需要的常量值。

  • default— 不指定参数时的默认值。
  • type — 命令行参数应该被转换成的类型。(str, int, bool..)
  • choices — 参数可允许的值的一个容器。
  • required — 可选参数是否可以省略 (仅针对可选参数)。
  • help — 参数的帮助信息,当指定为 argparse.SUPPRESS 时表示不显示该参数的帮助信息.
  • metavar — 在 usage 说明中的参数名称,对于必选参数默认就是参数名称,对于可选参数默认是全大写的参数名称.
  • dest — 解析后的参数名称,默认情况下,对于可选参数选取最长的名称,中划线转换为下划线.
1
2
3
4
parser.add_argument('--name', type=str, required=True, default='', help='名')

parser.add_argument('--foo', action='store_const', const=42) # 实际foo=42

是否输入了某参数

1
parser.add_argument('--properties', action='store_true', help=properties_desc) 

如上调用python arg.py时,若带参数—properties 则args.properties=True 否则为False

  • 基于阈值的图像分割
  • 边缘检测
  • 分离背景
  • K-Means聚类
  • 均值漂移算法
  • 分水岭算法
  • 图像漫水填充分割

阈值

图像分割的效果取决于临界值的选取

边缘检测

根据像素值变化的特点 根据梯度值确定边界

findContours 和 drawContours是基于边缘检测的统计方法

查找轮廓

GrabCut算法 设置掩码矩阵(蒙版)分离前景和背景

K-Means聚类

  • 第一步 确定K值 即将数据集分为K个族类
  • 第二部 从数据集中随机选择K个数据点作为质心(Centroid)

WebView2使用 Microsoft Edge(要知道Edge也是fork自Chromium的分发版本) 作为绘制引擎, 展示web内容的混合应用控件
可以认为Electron是WebView2的前辈 二者的原理是一样的

WebView2 runtime is available on most Windows 10 and Windows 11 machines by default. But it may not be available on older platforms.

若缺需要查看WebView2运行时是否安装 Microsoft Docs:检测是否已安装合适的 WebView2 运行时

webview vs webbrowser

WebBrowser类

PyQt 是 Qt 的Python版本,Qt是基于C++的GUI,在Python的UI组件库中,PyQt功能强大,提供QT Designer设计UI。PyQt 可与 C++ Qt无缝整合,另有QtWebEngine结合web前端实现Electron的功能。

题外话:Tkinter 是Python解决UI的原生库,优点在于没有其他依赖。但内容基础功能简单 比其他UI库都有不及

PyQt5类分为很多模块

QtCore 包含了核心的非GUI的功能。主要和时间、文件与文件夹、各种数据、流、URLs、mime类文件、进程与线程一起使用。
QtGui 包含了窗口系统、事件处理、2D图像、基本绘画、字体和文字类。QtWidgets类包含了一系列创建桌面应用的UI元素。 QtMultimedia包含了处理多媒体的内容和调用摄像头API的类。 QtNetwork包含了网络编程的类,这些工具能让TCP/IP和UDP开发变得更加方便和可靠。QtWebSockets包含了WebSocket协议的类。 QtWebKit包含了一个基WebKit2的web浏览器。

安装

1
2
pip install pyqt5
pip install pyqt5-tools

使用国内pip源

1
pip install -i https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple pyqt5

添加pyqt5-tools路径到环境变量Path

配置PyCharm外部工具
PyCharm -> 文件 -> 设置 -> 工具 -> 外部工具

填入QT designer路径
image.png

另添加 Pyuic 用于将ui文件转为py文件

须填入参数$FileName$ -o $FileNameWithoutExtension$.py
v2-7039dac81f16e3567988b1a16b745067_720w.jpg

类似的 可添加PyRCC

PyQt Hello World

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import sys
from PyQt5.QtWidgets import QApplication, QWidget


if __name__ == '__main__':

app = QApplication(sys.argv)

w = QWidget()
w.resize(250, 150)
w.setFixedSize(250, 150) # 固定大小
w.move(300, 300)
w.setWindowTitle('HelloWorld!')
w.show()

sys.exit(app.exec_())

QT designer

完成如上配置 可在PyCharm的外部工具打开 QT designer,
使用QT designer生成UI文件

从.ui文件到.py文件

1
pyuic demo.ui -o demo.py

生成的py文件是UI的类定义文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(800, 600)
self.centralWidget = QtWidgets.QWidget(MainWindow)
self.centralWidget.setObjectName("centralWidget")
self.btn_import = QtWidgets.QToolButton(self.centralWidget)
self.btn_import.setGeometry(QtCore, QRect(610, 20, 71, 22))
self.btn_import.setObjectName("btn_import")
self.btn_import.clicked.connect(self.on_clicked_btn_import)

def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
self.btn_import.setText(_translate("MainWindow", "import"))

def on_clicked_btn_import(self):
print("btn_import clicked")

.....

运行该UI需要在程序入口(__main__)实例化
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
import sys

from PyQt5.QtWidgets import QApplication, QMainWindow, QFileDialog, QMessageBox

from ui.mainWindow import Ui_MainWindow # 引入UI的类定义


class Startup(QMainWindow, Ui_MainWindow):
def __init__(self):
super(Startup, self).__init__()
self.setupUi(self)

def importImg(self):
fname = QFileDialog.getOpenFileName(self, 'import Image', './')

if fname[0]:
QMessageBox.information(self, 'import success', fname[0], QMessageBox.Yes)
print("import img is here")


if __name__ == '__main__':
app = QApplication(sys.argv)
startup = Startup()
startup.show()
sys.exit(app.exec_())


可见这里的Startup类即 Ui_MainWindow 实例化参数object 两个类是相互引用的;Startup类函数即主窗体的槽函数

事件、信号和槽函数

在控件层面 事件如点击按钮(clicked)选择菜单(triggered),事件可以直接connect到响应函数(槽函数)

1
2
3
4
btn = QPushButton('test', self)
btn.resize(btn.sizeHint())
btn.move(50, 50)
btn.clicked.connect(self.showDlg)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# MenuBar -> Menu -> Action -> Slot
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 842, 26))
self.menubar.setObjectName("menubar")
self.menu = QtWidgets.QMenu(self.menubar)
self.menu.setObjectName("menu")
MainWindow.setMenuBar(self.menubar)

self.actionimport = QtWidgets.QAction(MainWindow)
self.actionimport.setObjectName("actionimport")
self.menu.addAction(self.actionimport)
self.menubar.addAction(self.menu.menuAction())

self.retranslateUi(MainWindow)
self.actionimport.triggered.connect(MainWindow.importImg)
QtCore.QMetaObject.connectSlotsByName(MainWindow)

或者,不同事件发送某信号,而某个模块监听到该信号而触发响应
1
2
3
4
5
6
7
8
9
10
11
# 创建一个信号closeApp。当触发鼠标点击事件时信号会被发射。信号连接到QMainWindow的close()方法。
class Communicate(QObject):
closeApp = pyqtSignal()
# 信号使用了pyqtSignal()方法创建,并且成为外部类Communicate类的属性。
self.c = Communicate()
self.c.closeApp.connect(self.close)
# 把自定义的closeApp信号连接到QMainWindow的close()槽上。
def mousePressEvent(self, event):

self.c.closeApp.emit()
# 当我们在窗口上点击一下鼠标,closeApp信号会被发射。应用中断。

封装

pyinstaller vs Nuitka

Nuitka(音妞卡)是将python程序转换成C语言的可执行elf文件。这样在运行时就可以享受到C语言处理过程中的优化,提高速度。

1
pip install -U nuitka

编译依赖Python和C compiler,c compiler需支持C11或C++03 这意味着需安装MinGW64 C11编译器 基于gcc11.2或更高版本,或visual studio

用户刷新/访问资源行为的不同方式,会采用不同的缓存策略

  • 地址栏/链接访问url 强制缓存 在缓存数据未失效(max-age范围内)的情况下,可以直接使用缓存数据,不需要再请求服务器
  • F5/刷新/右键菜单重新加载
  • Ctl+F5 (完全不使用HTTP缓存)

对比缓存: 浏览器第一次请求数据时,服务器会将缓存标识与数据一起返回给浏览器,浏览器将二者备份至缓存数据库中。
当浏览器再次请求数据时,浏览器将备份的缓存标识发送给服务器,服务器根据缓存标识进行判断,判断成功后,返回304状态码,通知客户端比较成功,可以使用缓存数据。

对于对比缓存,响应 header 中会有两个字段来标明规则:Last-Modified / If-Modified-Since
服务器响应请求时,会告诉浏览器一个告诉浏览器资源的最后修改时间:Last-Modified,浏览器之后再请求的时候,会带上一个头:If-Modified-Since,这个值就是服务器上一次给的 Last-Modified 的时间,服务器会比对资源当前最后的修改时间,如果大于If-Modified-Since,则说明资源修改过了,浏览器不能再使用缓存,否则浏览器可以继续使用缓存,并返回304状态码。Etag / If-None-Match(优先级高于Last-Modified / If-Modified-Since)


返回304结果的资源调用了协商缓存

HTTP 缓存主要是通过请求和响应报文头中的对应 Header 信息,来控制缓存的策略。
Cache-Control 是最重要的规则。常见的取值有 private、public、no-cache、max-age,no-store,默认为 private。

  • max-age:用来设置资源(representations)可以被缓存多长时间,单位为秒;
  • s-maxage:和 max-age 是一样的,不过它只针对代理服务器缓存而言;
  • public:指示响应可被任何缓存区缓存;
  • private:只能针对个人用户,而不能被代理服务器缓存;
  • no-cache:强制客户端直接向服务器发送请求,也就是说每次请求都必须向服务器发送。服务器接收到请求,然后判断资源是否变更,是则返回新内容,否则返回304,未变更。这个很容易让人产生误解,使人误以为是响应不被缓存。实际上Cache-Control: no-cache是会被缓存的,只不过每次在向客户端(浏览器)提供响应数据时,缓存都要向服务器评估缓存响应的有效性。
  • no-store:禁止一切缓存(这个才是响应不被缓存的意思)。

私有缓存和共享缓存

私有缓存通常存在浏览器端,只存在本地,不会与其他客户端共享,因此可以保存个性化设置,部分资料中区分“浏览器缓存”和私有Http缓存的概念,“浏览器缓存”指localstorage sessionstorage cookies等

1
Cache-Control: private

共享缓存可细分为代理缓存和托管缓存,代理缓存是网络中间代理提供的缓存,在Https普及的现状下应用收到局限,托管缓存由服务开发人员明确部署,以降低源服务器负载并有效地交付内容。如反向代理、CDN 和 service worker 与缓存 API 的组合。

开发

对前端来说,缓存是缓存在前端,但实际上代码是后端的同学来写的。如果你需要实现前端缓存的话啊,通知后端的同学加响应头就好了。

以node.js为例 对于需要强缓存的资源

1
res.setHeader('Cache-Control', 'public, max-age=xxx');

协商缓存
1
2
3
res.setHeader('Cache-Control', 'public, max-age=0');
res.setHeader('Last-Modified', xxx);
res.setHeader('ETag', xxx);

npmjs: fresh