<?xml version="1.0" encoding="utf-8" standalone="yes"?><search><entry><title>JVM历史回顾与优化</title><url>https://www.d-j.fun/post/notes/2024/0317-jvm-history/</url><categories><category>notes</category></categories><tags><tag>jvm</tag></tags><content type="html"> Java虚拟机（JVM）自1995年Java语言发布以来，一直是Java技术的核心。它不仅支持Java语言，还经历了从最初的性能优化到安全性增强，以及对多语言的支持等一系列发展和变革。
JVM的发展历程 1995年：Sun Microsystems发布Java技术，包括Java语言、JVM和Java API，JVM旨在实现“一次编写，到处运行”的理念。 1996-2006年：随着Java技术的发展，JVM经历了持续的优化和改进，如Java 2的推出增强了企业级功能，Java 5引入了泛型和注解等特性。 2010年：Oracle Corporation收购Sun Microsystems，接管Java和JVM的开发。 2011-2014年：Oracle发布Java SE 7和Java SE 8，带来了动态语言支持和Lambda表达式等重大更新。 2017年及以后：Java SE 9引入模块化系统，并随后的版本通过更频繁的更新，快速迭代JVM的功能和性能。 JVM的持续优化 性能优化：从解释执行到即时编译（JIT），提高代码执行效率，并引入先进的垃圾回收算法（如G1 GC、ZGC）优化内存管理。 安全性增强：通过类加载机制和字节码验证器加强安全性，同时提供沙箱环境限制恶意代码。 跨语言支持：JVM不再仅仅支持Java，InvokeDynamic特性增强了对动态语言的支持。 调试和监控工具：如JConsole、VisualVM、Java Flight Recorder等工具帮助开发者进行性能调优和问题诊断。 模块化系统：Java 9的模块化系统（Project Jigsaw）改善了依赖管理，提升了系统安全性和性能。 即时编译优化：通过逃逸分析、循环优化、内联等技术，JIT编译器不断提升程序运行效率。 垃圾回收机制：持续进化的GC技术，旨在减少GC暂停时间，特别是对于大型、内存密集型应用。 JVM从其诞生之初的简单虚拟机，发展成为今天高效、多功能、跨语言的执行平台，体现了技术的持续进化和对现代软件开发需求的适应。随着不断的技术创新和优化，JVM将继续在全球软件开发领域发挥核心作用。</content></entry><entry><title>设计一个视频帧选择器界面</title><url>https://www.d-j.fun/post/notes/2024/0315-pyside6-video-selector/</url><categories><category>notes</category></categories><tags><tag>pyside</tag></tags><content type="html"> 在本文中，我们将使用Python和PySide6库设计一个简单的界面，用于选择视频中的特定帧。该界面将包括一个视频帧显示区域、一个滑动条用于选择帧、以及一个确认选择的按钮。
准备工作 首先，确保你已经安装了Python和PySide6库。你还需要安装OpenCV库，以便处理视频文件。你可以使用以下命令来安装所需的库：
pip install PySide6 opencv-python-headless 代码实现 下面是我们设计的视频帧选择器界面的Python代码：
import sys from PySide6.QtWidgets import QApplication, QMainWindow, QLabel, QPushButton, QVBoxLayout, QWidget, QSlider from PySide6.QtCore import Qt import cv2 class VideoFrameSelector(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle(&amp;#34;视频帧选择器&amp;#34;) self.video_path = None self.current_frame = 0 self.video_label = QLabel() self.video_label.setAlignment(Qt.AlignCenter) self.slider = QSlider(Qt.Horizontal) self.slider.setMinimum(0) self.slider.setMaximum(0) self.slider.valueChanged.connect(self.change_frame) self.confirm_button = QPushButton(&amp;#34;确认选择&amp;#34;) self.confirm_button.clicked.connect(self.confirm_selection) layout = QVBoxLayout() layout.addWidget(self.video_label) layout.addWidget(self.slider) layout.addWidget(self.confirm_button) central_widget = QWidget() central_widget.setLayout(layout) self.setCentralWidget(central_widget) def load_video(self, video_path): self.video_path = video_path cap = cv2.VideoCapture(video_path) total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) self.slider.setMaximum(total_frames - 1) cap.release() def change_frame(self, value): self.current_frame = value cap = cv2.VideoCapture(self.video_path) cap.set(cv2.CAP_PROP_POS_FRAMES, self.current_frame) ret, frame = cap.read() if ret: frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) height, width, channel = frame_rgb.shape bytes_per_line = 3 * width qt_img = QImage(frame_rgb.data, width, height, bytes_per_line, QImage.Format_RGB888) self.video_label.setPixmap(QPixmap.fromImage(qt_img)) cap.release() def confirm_selection(self): print(&amp;#34;选中帧:&amp;#34;, self.current_frame) if __name__ == &amp;#34;__main__&amp;#34;: app = QApplication(sys.argv) window = VideoFrameSelector() window.setGeometry(100, 100, 800, 600) window.show() sys.exit(app.exec()) 当你运行这个程序时，它首先创建了一个名为&amp;quot;视频帧选择器&amp;quot;的窗口。在这个窗口中，你可以加载一个视频文件，并通过滑动滑动条来选择视频的特定帧。程序还提供了一个&amp;quot;确认选择&amp;quot;按钮，用于确认你选择的帧。
程序的主要组件包括一个用于显示视频帧的标签(video_label)、一个滑动条(slider)和一个确认选择按钮(confirm_button)。通过滑动滑动条，你可以选择视频的不同帧，并在标签中显示所选帧的视频画面。当你点击确认选择按钮时，程序将打印出你选择的帧数。
在程序的主函数中，它创建了一个应用程序对象(QApplication)和一个窗口对象(VideoFrameSelector)。然后设置窗口的大小和位置，并将窗口显示出来。程序将在用户关闭窗口后退出。
当你运行程序时，你可以通过加载视频文件，并使用滑动条选择不同的帧来查看视频的内容。当你找到想要的帧时，可以点击确认选择按钮，程序将在控制台中输出你选择的帧数。
总的来说，这个程序演示了如何使用Python和PySide6库创建一个简单的视频帧选择器界面，以便于用户在视频文件中选择特定的帧。</content></entry><entry><title>ChatGPT、Midjourney、PixVerse结合：打造动态壁纸APP</title><url>https://www.d-j.fun/post/notes/2024/0312-aura-live-wallpaper/</url><categories><category>notes</category></categories><tags><tag>chatgpt</tag><tag>midjourney</tag><tag>pixverse</tag></tags><content type="html"> 体验地址 App名称：Aura Live Wallpaper https://play.google.com/store/apps/details?id=com.auralive.wallpaper
在本博客中，我们将探索如何将ChatGPT、Midjourney和PixVerse这三种技术结合起来，创建一个动态壁纸应用程序。我们将从基础知识出发，深入到核心技术，让你理解这三种技术如何协同工作以生成和管理动态壁纸。
引言 动态壁纸，不仅仅是一种视觉体验的提升，它也代表着个性化和创意的表达。在智能设备日益普及的今天，用户对于设备的个性化需求日益增长，动态壁纸因其生动、有趣的特性而受到广泛欢迎。
概要 ChatGPT：一个由OpenAI开发的语言生成模型，能够理解和生成自然语言文本。 Midjourney：一个高级的图像合成工具，可以将文本描述转化为高质量的图像。 PixVerse：一款可以实现图像编辑和动态效果添加的软件。 结合这三者，我们可以创建一个动态壁纸APP，通过ChatGPT生成壁纸主题的描述，用Midjourney根据描述生成图像，最后通过PixVerse为这些图像添加动态效果。
核心知识展示 1. 聊天机器人(ChatGPT) ChatGPT能够理解用户的输入并生成连贯、有逻辑的回复，这在动态壁纸APP中可用于生成壁纸的文本描述。例如，用户想要一款表现“宁静森林”的壁纸，ChatGPT可以根据这一需求生成详细的描述文本。
2. 图像生成(Midjourney) Midjourney可以根据文本描述生成图像。利用ChatGPT提供的描述，Midjourney能够创造出符合用户需求的高质量图片。这些图片可以是静态的，也可以是包含多个元素的复杂场景，为下一步的动态化提供基础。
3. 动态化处理(PixVerse) PixVerse则是将静态图片转变为动态壁纸的关键。它可以在Midjourney生成的图像上添加各种动态效果，如水流、风吹树叶等自然现象，或者更加抽象的视觉效果，如颜色变化、图像变形等。
结合三者的工作流程 用户在APP上输入想要的壁纸主题。 ChatGPT根据主题生成描述文本。 Midjourney使用这些文本生成图像。 PixVerse对这些图像进行动态化处理，生成最终的动态壁纸。 技术应用 为了更好地理解这个过程，让我们通过一段伪代码来展示这个集成过程：
def create_dynamic_wallpaper(theme): # 使用ChatGPT生成文本描述 description = chatgpt.generate_description(theme) # 使用Midjourney根据描述生成图像 image = midjourney.create_image(description) # 使用PixVerse添加动态效果 dynamic_wallpaper = pixverse.add_dynamic_effects(image) return dynamic_wallpaper 在这个示例中，chatgpt.generate_description、midjourney.create_image 和 pixverse.add_dynamic_effects 分别代表三个系统中的功能模块。
结语 通过结合ChatGPT的语言生成能力、Midjourney的图像创造能力和PixVerse的动态效果添加功能，我们可以创造出既美观又个性化的动态壁纸。这样的集成应用不仅满足了用户对壁纸个性化的需求，还开启了艺术创作和技术融合的新领域。</content></entry><entry><title>最新Stable Diffusion下载+安装+使用教程（超详细版本）</title><url>https://www.d-j.fun/post/notes/2024/0310-sd-install/</url><categories><category>notes</category></categories><tags><tag>sd</tag></tags><content type="html"> 前言
随着AI绘画技术的不断发展，Stable Diffusion（SD）作为一种深度学习文本到图像生成模型，受到了越来越多人的关注和应用。本文将基于最新的SD整合包，结合笔者整合的资源，为大家介绍最基础的概念和安装方式，使更多人能够轻松上手SD模型，并享受到AI绘画的乐趣。
Stable Diffusion（SD）是什么 Stable Diffusion是一个由Stability AI与多个学术研究者和非营利组织合作开发的深度学习文本到图像生成模型。自2022年发布以来，SD源代码和模型已在GitHub上开源，并由AUTOMATIC1111维护。SD模型具有离线运行的特性，适用于大多数配备至少8GB显存的适度GPU的消费级硬件。开源社区为SD的普及做出了重要贡献，使更多人能够参与到AI绘画的领域中。
SD基本概念 SD模型涉及多个概念和组件，其中包括：
大模型： 作为生成图像的底料，直接影响生成图像的大方向。 VAE： 类似于滤镜，用于稳定画面的色彩范围。 LoRA： 可以在中小范围内影响出图的风格，是大模型的补充。 ControlNet： 为SD模型提供了“眼睛”，使其能够根据现有图片获取线条或景深等信息。 Stable Diffusion Web-UI（SD-WEBUI）： 是一个开源软件，基于Stability AI算法制作，能够通过图形界面操控SD模型。 秋叶包： 是中国开发者秋叶开发的整合包，为SD的部署和使用提供了便利。 安装Stable Diffusion 步骤一：安装运行依赖 在开始安装Stable Diffusion之前，首先需要安装运行依赖。请双击运行“启动器运行依赖-dotnet-6.0.11.exe”文件，并按照提示完成安装。
步骤二：解压安装包 下载并解压“sd-webui-aki-v4.zip”安装包。您可以选择将其解压到任意您喜欢的目录中，以便进行下一步操作。
步骤三：导入核心数据 将下载好的大模型、ControlNet模型和LoRA模型放置到相应的目录下。确保大模型放置在\sd-webui-aki-v4\models\Stable-diffusion目录下，ControlNet模型放置在\sd-webui-aki-v4\models\ControlNet目录下，LoRA模型放置在\sd-webui-aki-v4\extensions\sd-webui-additional-networks\models\lora目录下。
步骤四：下载推荐大模型 下载推荐大模型文件夹中的模型，并将所有模型放置到\sd-webui-aki-v4\models\Stable-diffusion目录下。
步骤五：下载ControlNet模型 下载ControlNet模型文件夹中的模型，并将所有内容放置到\sd-webui-aki-v4\models\ControlNet目录下。
步骤六：下载推荐LoRA 下载LoRA模型文件夹中的所有LoRA模型，并将其全部放置到\sd-webui-aki-v4\extensions\sd-webui-additional-networks\models\lora目录下。这样，您就完成了Stable Diffusion的安装准备工作。
使用Stable Diffusion 1. 开启软件运行 双击“A启动器.exe”，点击“一键启动”按钮。等待软件自动打开浏览器网页或手动输入网址：http://127.0.0.1:7860 ，确保软件已经启动成功。
2. 生成图像 在SD-WEBUI界面中输入您想要生成图像的关键词，然后点击“生成”按钮。等待一段时间后，您将会看到生成的图像结果。
结论 现在您已经成功地安装了Stable Diffusion模型，您可以尝试使用不同的大模型、LoRA和ControlNet模型来生成多样化的图像。通过尝试不同的参数和组合，您将可以探索到更多有趣的图像生成效果。
为了进一步提升图像生成的效果，您可以尝试调整SD模型的参数或者在训练时使用不同的数据集。通过对模型进行微调和优化，您可以获得更加满意的图像生成结果，同时也可以提高SD模型的性能和稳定性。
在使用SD模型的过程中，您可能会遇到一些问题或者有一些新的想法和需求。您可以通过查阅SD模型的官方文档、访问相关的论坛和社区，或者参考其他用户的经验和分享来解决问题，获取灵感，不断地提升自己的图像生成技能。
最重要的是，不断地练习和尝试是掌握SD模型的关键。只有通过不断地实践和探索，您才能够深入理解SD模型的原理和特性，熟练</content></entry><entry><title>Python GUI技术方案分析及选型</title><url>https://www.d-j.fun/post/notes/2024/0308_python_gui_select/</url><categories><category>notes</category></categories><tags><tag>pyside</tag></tags><content type="html"> Python GUI技术方案分析及选型 Python作为一门广泛使用的编程语言，其图形用户界面（GUI）开发选项多样，本文将逐一介绍几种主要的Python GUI技术方案，从而帮助开发者进行合理的选型。
引言 GUI是软件开发中不可或缺的一部分，它使得用户能够直观、方便地与程序进行交互。Python虽然以其在数据科学、网络开发中的应用最为人所熟知，但它在GUI开发方面也具有不错的表现。
概要 Python GUI开发的几种技术方案主要包括：
Tkinter PyQt WxPython Kivy 下面将详细介绍每种技术的特点、适用场景及示例代码。
Tkinter Tkinter是Python的标准GUI库，它为多数Python解释器提供了内置支持，因此不需要额外安装。
优点：
简单易学，适合快速制作简单的GUI程序。 跨平台，能够在Windows、Linux、Mac OS上运行。 缺点：
界面风格相对陈旧，不适合制作外观现代化的应用。 功能较为有限。 示例代码：
import tkinter as tk root = tk.Tk() root.title(&amp;#34;Hello Tkinter&amp;#34;) label = tk.Label(root, text=&amp;#34;Hello, world!&amp;#34;) label.pack() root.mainloop() PyQt PyQt是一套由Riverbank Computing Limited开发的Python绑定的跨平台GUI工具包。它封装了Qt库，这是目前最强大的GUI库之一。
优点：
功能强大，适用于开发复杂的商业级应用。 支持Qt Designer，可以可视化设计界面。 缺点：
学习曲线陡峭。 开源版本在商业项目中使用有一定的限制。 示例代码：
from PyQt5.QtWidgets import QApplication, QLabel app = QApplication([]) label = QLabel(&amp;#39;Hello, PyQt!&amp;#39;) label.show() app.exec_() WxPython WxPython是Python的另一个强大的GUI库，它是wxWidgets C++库的Python封装。
优点：
界面美观，跨平台。 社区支持良好。 缺点：
文档相对较少。 示例代码：
import wx app = wx.App(False) frame = wx.Frame(None, wx.ID_ANY, &amp;#34;Hello WxPython&amp;#34;) frame.Show(True) app.MainLoop() Kivy Kivy是专为触控设备设计的Python GUI框架，支持多点触控和各种手势操作。
优点：
适合开发现代触控应用。 跨平台，包括对Android和iOS的支持。 缺点：
相比其他框架，社区和资源较少。 示例代码：
from kivy.app import App from kivy.uix.label import Label class MyApp(App): def build(self): return Label(text=&amp;#39;Hello, Kivy!&amp;#39;) MyApp().run() 选型 在选择Python GUI框架时，应考虑以下因素：
项目需求：是制作简单的工具还是复杂的商业软件？ 开发时间：快速开发与深度定制的需求之间需要平衡。 学习曲线：是否有时间和资源去学习一个复杂的框架？ 社区和文档支持：框架的问题解决方案是否容易找到？ 综上所述，对于简单项目和快速原型开发，Tkinter是一个不错的选择。如果你需要开发外观现代化、功能复杂的应用程序，PyQt或WxPython可能是更好的选择。而对于开发触控屏应用，Kivy无疑是最合适的框架。
通过上述分析，希望你能够根据自己的具体需求和背景，做出合适的Python GUI技术选型。</content></entry><entry><title>深入探索篡改猴：用户脚本管理的极致体验</title><url>https://www.d-j.fun/post/notes/2024/0308_chrome_user_script/</url><categories><category>notes</category></categories><tags><tag>tampermonkey</tag></tags><content type="html"> 篡改猴（Tampermonkey）作为一款领先的浏览器用户脚本管理器，提供了一种强大的方式来个性化和增强网页功能。通过安装篡改猴，用户可以在浏览器中运行自定义的JavaScript脚本，以调整网页的行为和外观。本文将详细介绍篡改猴的特性、实际应用，以及哪里可以找到优质的用户脚本。
篡改猴的核心特性 篡改猴在用户脚本管理方面提供了丰富的功能和灵活性：
广泛的浏览器支持：篡改猴与多种主流浏览器兼容，包括Chrome、Firefox、Edge和Safari，确保不同平台的用户都能使用。 便捷的脚本管理：用户可以轻松安装、编辑、启用/禁用及删除脚本，管理界面友好，操作直观。 高度兼容性：支持广泛的用户脚本元数据块，确保多数由GreaseMonkey等其他管理器开发的脚本都能在篡改猴上运行。 脚本同步功能：通过云服务如Google Drive或Dropbox同步脚本，方便在不同设备间共享和管理。 更新安全性：篡改猴会自动检测脚本更新，帮助保持脚本的最新状态和安全性。 脚本获取渠道 用户可以从多个平台获取所需的篡改猴脚本，主要的脚本来源包括：
Greasy Fork
简介：Greasy Fork是一个著名的用户脚本社区，提供了多种语言的脚本，满足不同用户的需求。 特点：这个平台上的脚本种类繁多，从界面美化、功能增强到自动化操作等都一应俱全。 链接：https://greasyfork.org/ OpenUserJS
简介：OpenUserJS是一个国际性的开放用户脚本社区，开发者和用户可以在此分享和下载用户脚本。 特点：提供了一个开放平台，便于用户和开发者交流、分享脚本。 链接：https://openuserjs.org/ GitHub
简介：许多开发者会在GitHub上分享他们的用户脚本。 特点：GitHub上的脚本通常是开源的，用户可以自由下载、修改和分发。 链接：访问开发者的GitHub页面获取脚本。 实际应用示例 通过篡改猴，用户可以实现多样化的网页定制和自动化操作：
自动化操作：自动填充登录信息、自动点击按钮等。 网页定制：修改网站的样式、布局调整、隐藏不需要的页面元素等。 功能增强：为网站添加额外的功能，比如视频下载、数据抓取等。 类似的插件对比 除了篡改猴，还有其他用户脚本管理器，如GreaseMonkey、Violentmonkey和FireMonkey，每个都有其特点：
GreaseMonkey：主要针对Firefox浏览器，是用户脚本管理的先驱。 Violentmonkey：支持多种浏览器，功能与篡改猴类似，界面简洁。 FireMonkey：专为Firefox设计的用户脚本管理器，强调性能和安全性，支持用户脚本和样式表。 结论 篡改猴提供了强大的脚本管理和自定义网页功能，为用户提供了改善和增强网页体验的可能性。用户可以根据自己的需求选择合适的用户脚本管理器，以提升浏览效率和体验。</content></entry><entry><title>制作自己的篡改猴Tampermonkey脚本</title><url>https://www.d-j.fun/post/notes/2024/0308_self_user_script/</url><categories><category>notes</category></categories><tags><tag>tampermonkey</tag></tags><content type="html"> 制作一个Tampermonkey脚本允许你定制和改善你的浏览体验。下面是制作一个基本的Tampermonkey脚本的步骤，以及一些关键的代码示例。
步骤 1: 安装Tampermonkey扩展 首先，确保你的浏览器已安装Tampermonkey扩展。
在Chrome中，访问Chrome 网上应用店搜索并安装Tampermonkey。 在Firefox中，访问Firefox 浏览器附加组件搜索并安装Tampermonkey。 步骤 2: 创建新脚本 点击浏览器工具栏中的Tampermonkey图标。 选择“创建新脚本&amp;hellip;”。 Tampermonkey会打开一个新的脚本编辑器窗口。 步骤 3: 编写脚本元数据 在脚本编辑器中，首先需要定义脚本的元数据部分，它告诉Tampermonkey如何管理脚本。
// ==UserScript== // @name My First Tampermonkey Script // @namespace http://tampermonkey.net/ // @version 1.0 // @description try to take over the world! // @author Your Name // @match https://*/* // @grant none // ==/UserScript== @name: 脚本名称。 @namespace: 脚本的命名空间。 @version: 脚本版本号。 @description: 脚本描述。 @author: 脚本作者。 @match: 定义脚本应当运行的页面。 @grant: 指定脚本权限，none表示不需要额外权限。 步骤 4: 添加脚本逻辑 在元数据定义之后，开始编写脚本的主体逻辑。 (function() { &amp;#39;use strict&amp;#39;; // Your code here... console.log(&amp;#39;Tampermonkey script running.&amp;#39;); })(); 在这个例子中，脚本仅在控制台打印一条消息，你可以根据需要添加自己的逻辑。
步骤 5: 保存和测试脚本 在脚本编辑器中点击“文件”&amp;gt;“保存”或直接使用快捷键（例如Ctrl + S）保存脚本。 访问你在@match指令中指定的网站，检查脚本是否按预期运行。
步骤 6: 调试脚本 如果脚本没有按预期运行，可以通过浏览器的开发者工具(Console标签页)查看日志和错误信息来进行调试。
通过以上步骤，你可以创建和测试自己的Tampermonkey脚本。随着实践的增加，你将能够创建更复杂和功能强大的脚本来改善网页的使用体验。
这个Markdown文档提供了详细的步骤和基本的代码示例，帮助你开始制作和使用Tampermonkey脚本。</content></entry><entry><title>基于qt-material快速开发桌面应用</title><url>https://www.d-j.fun/post/notes/2024/0307_pyside_and_qt-material/</url><categories><category>notes</category></categories><tags><tag>pyside</tag></tags><content type="html"> 使用PySide6快速构建桌面端程序 引言 PySide6，作为Qt for Python的官方集合，提供了Python语言绑定，允许开发者使用Python编程语言来创建Qt应用程序。这种结合了Qt的强大功能和Python简洁语法的方式，让开发现代桌面应用变得更加容易和快捷。本文将探讨如何在PySide6中应用Qt Material风格，来构建既美观又实用的桌面端程序。
概要 在PySide6项目中应用Qt Material风格包含以下几个步骤：
环境搭建：安装PySide6和Qt Material。 界面设计：使用Qt Designer设计界面。 应用样式：将Qt Material样式应用到PySide6应用。 实现功能：编写Python代码实现应用逻辑。 环境搭建 首先需要确保Python已经安装在你的系统中。接下来，通过pip安装PySide6：
pip install PySide6 目前，Qt Material直接支持可能是有限的，需要通过QML或者查找第三方Python库来实现Material Design风格。如果使用QML，可以结合Qt Quick进行设计。
界面设计 可以使用Qt Designer设计界面，这是一个拖放式的GUI设计工具。虽然Qt Designer默认不包含Material Design的组件，但你可以设计基础的布局和控件，之后通过样式表或QML来应用Material Design风格。
设计完成后，保存UI文件，PySide6可以加载这些UI文件来构建用户界面。
应用样式 虽然PySide6并不直接提供Material Design组件，但你可以使用QML来实现Material Design风格的界面，或者使用CSS-like的样式表来模拟这种风格。
如果选择QML，你可以这样开始：
创建一个QML文件来定义界面。 在PySide6应用中加载这个QML文件。 以下是一个QML示例，展示如何定义一个简单的Material Design按钮：
import QtQuick 2.0 import QtQuick.Controls 2.0 import QtQuick.Controls.Material 2.0 ApplicationWindow { visible: true width: 640 height: 480 title: &amp;#34;Qt Material in PySide6&amp;#34; Button { text: &amp;#34;Click me&amp;#34; anchors.centerIn: parent Material.theme: Material.Light onClicked: messageDialog.open() } MessageDialog { id: messageDialog text: &amp;#34;Hello, World!&amp;#34; } } 实现功能 在PySide6中，你将使用Python来实现应用逻辑。下面是一个简单的例子，展示了如何加载QML文件并运行应用：
import sys from PySide6.QtWidgets import QApplication from PySide6.QtQuick import QQuickView from PySide6.QtCore import QUrl app = QApplication(sys.argv) view = QQuickView() view.setSource(QUrl(&amp;#39;view.qml&amp;#39;)) view.show() sys.exit(app.exec_()) 在这个例子中，QQuickView用于加载和显示QML文件。这使得将QML中的Material Design风格界面和Python后端逻辑无缝结合成为可能。
通过以上步骤，你可以在PySide6应用中应用Material Design风格，构建出既美观又实用的桌面端应用程序。结合Python语言的易用性和Qt的强大功能，你可以有效地提升开发效率和用户体验。</content></entry><entry><title>Excel常用函数</title><url>https://www.d-j.fun/post/notes/2024/0305_excel_func/</url><categories><category>notes</category></categories><tags><tag>excel</tag></tags><content type="html"> Excel 常用函数简介 Microsoft Excel 是一款广泛使用的电子表格软件，凭借其强大的数据处理能力，在各行各业中都有应用。Excel 提供了大量的函数，帮助用户进行复杂的数据计算、分析和处理。本文将简要介绍一些在日常工作中最为常用的 Excel 函数，包括它们的用途和基本用法。
文本处理函数 1. CONCATENATE / TEXTJOIN 用途：连接两个或多个字符串。 示例： CONCATENATE(A1, B1)：连接 A1 和 B1 单元格中的文本。 TEXTJOIN(&amp;quot;,&amp;quot;, TRUE, A1:A3)：将 A1 到 A3 的文本用逗号连接，忽略空值。 2. LEFT / RIGHT / MID 用途：从文本字符串中提取字符。 示例： LEFT(A1, 2)：提取 A1 单元格文本的左侧两个字符。 RIGHT(A1, 3)：提取 A1 单元格文本的右侧三个字符。 MID(A1, 2, 3)：从 A1 单元格文本的第二个字符开始提取长度为三的字符串。 3. UPPER / LOWER / PROPER 用途：转换文本的大小写。 示例： UPPER(A1)：将 A1 单元格的文本转换为大写。 LOWER(A1)：将 A1 单元格的文本转换为小写。 PROPER(A1)：将 A1 单元格的文本转换为首字母大写。 数值处理函数 1. SUM / AVERAGE 用途：计算数值的总和或平均值。 示例： SUM(A1:A10)：计算 A1 到 A10 单元格中数值的总和。 AVERAGE(A1:A10)：计算 A1 到 A10 单元格中数值的平均值。 2. MAX / MIN 用途：找出一系列数值中的最大值或最小值。 示例： MAX(A1:A10)：找出 A1 到 A10 单元格中的最大值。 MIN(A1:A10)：找出 A1 到 A10 单元格中的最小值。 3. ROUND / ROUNDUP / ROUNDDOWN 用途：对数值进行四舍五入或指定方向的舍入。 示例： ROUND(A1, 2)：将 A1 单元格的数值四舍五入到小数点后两位。 ROUNDUP(A1, 1)：将 A1 单元格的数值向上舍入到小数点后一位。 ROUNDDOWN(A1, 1)：将 A1 单元格的数值向下舍入到小数点后一位。 逻辑和查找函数 1. IF 用途：基于条件进行逻辑判断。 示例： IF(A1&amp;gt;10, &amp;quot;High&amp;quot;, &amp;quot;Low&amp;quot;)：如果 A1 单元格的数值大于 10，返回 &amp;ldquo;High&amp;rdquo;；否则返回 &amp;ldquo;Low&amp;rdquo;。 2. VLOOKUP / HLOOKUP 用途：在表格中查找数据。 示例： VLOOKUP(value, table_range, column_index, FALSE)：在指定的表格范围内垂直查找值，并返回相同行的指定列的值。FALSE 表示精确匹配。 HLOOKUP(value, table_range, row_index, FALSE)：在指定的表格范围内水平查找值，并返回相同列的指定行的值。 3. AND / OR 用途：组合多个条件判断。 示例： IF(AND(A1&amp;gt;10, B1&amp;lt;5), &amp;quot;Yes&amp;quot;, &amp;quot;No&amp;quot;)：如果 A1 大于 10 且 B1 小于 5，返回 &amp;ldquo;Yes&amp;rdquo;；否则返回 &amp;ldquo;No&amp;rdquo;。 IF(OR(A1&amp;gt;10, B1&amp;lt;5), &amp;quot;Yes&amp;quot;, &amp;quot;No&amp;quot;)：如果 A1 大于 10 或 B1 小于 5，返回 &amp;ldquo;Yes&amp;rdquo;；否则返回 &amp;ldquo;No&amp;rdquo;。 总结 Excel 的函数功能强大，上述介绍的只是冰山一角。熟练掌握这些常用函数，可以大幅提高数据处理的效率和准确性。随着实践的增加，你将能够解锁更多高级功能和技巧，让 Excel 成为你日常工作的得力助手。</content></entry><entry><title>MediaPipe 简单测试</title><url>https://www.d-j.fun/post/notes/2024/0305_mediapipe_test/</url><categories><category>notes</category></categories><tags><tag>mediapipe</tag></tags><content type="html"> MediaPipe: 简化复杂媒体处理的开源框架 在当今数字化时代，实时视频处理和分析变得越来越重要。无论是社交媒体的滤镜、虚拟现实应用还是高级监控系统，背后都需要强大而复杂的技术支持。这就是MediaPipe闪亮登场的时刻，一个由Google开发的开源跨平台框架，旨在构建性能优化的媒体处理管道。
引言：为什么MediaPipe如此特别？ 在深入探讨MediaPipe之前，让我们先思考一个问题：在实时处理复杂媒体流（如视频、音频和图像序列）时，开发者面临哪些挑战？
首先，性能是一个巨大的考验。实时应用要求高效的数据处理，以保证流畅的用户体验。其次，多平台兼容性也是必须考虑的因素，开发者希望他们的应用能够跨操作系统工作，无论是Android、iOS还是Web。最后，快速原型开发和迭代也同样重要，以便快速响应市场变化。
MediaPipe应运而生，提供了一个高效、灵活且易于使用的解决方案，让开发者可以更容易地构建和部署复杂的媒体处理模型。
概要：MediaPipe的核心特性 MediaPipe框架的设计充满了创新，它提供了一系列的特性，让媒体处理变得更加高效和灵活：
跨平台支持：支持Android、iOS、Web和桌面平台，让你的应用可以广泛传播。 预构建模型：集成了多种预构建的机器学习模型，如手势识别、面部标记、对象检测和跟踪等，加速开发过程。 灵活的图形框架：基于图(graph)的框架设计，允许开发者灵活定义数据流通过的处理节点。 性能优化：针对移动设备优化，确保高效处理和低延迟。 核心知识展示：MediaPipe的工作原理 MediaPipe的魔法在于其背后的图形处理系统。每一个应用都被定义为一个图，其中的节点（称为Calculator）负责执行特定的处理任务，如视频帧的捕获、图像的变换、特征的提取等。数据以包(Packet)的形式在节点间流动，每个包可以包含任意类型的数据，如图像、视频帧、音频样本或简单的数字。
数据流和节点 考虑一个简单的例子：一个实时面部识别应用。在这个应用的MediaPipe图中，首先有一个视频帧捕获节点，负责从摄像头获取数据。然后，数据包被送到面部检测节点，该节点使用机器学习模型识别出视频中的面部。最后，结果可以被送到一个渲染节点，在用户界面上绘制面部边框。
graph LR A[视频帧捕获] --&amp;gt; B[面部检测] B --&amp;gt; C[渲染面部边框] 优化与兼容性 MediaPipe利用GPU加速（在支持的平台上）和多线程来优化性能，确保即使是在低功耗设备上也能实现实时处理。同时，通过提供各种预构建的模型和处理节点，MediaPipe简化了跨平台应用的开发流程。
展示代码片段：使用MediaPipe进行手势识别 让我们通过一个简单的示例来看看如何使用MediaPipe进行手势识别。以下是一个基本的代码片段，展示了如何在Python中设置和使用MediaPipe的手势识别模型。
import mediapipe as mp # 初始化MediaPipe手势识别 mp_hands = mp.solutions.hands hands = mp_hands.Hands() # 处理图像并识别手势 results = hands.process(image) # 打印识别到的手势信息 if results.multi_hand_landmarks: for hand_landmarks in results.multi_hand_landmarks: print(hand_landmarks) 这段代码首先导入MediaPipe库，然后初始化手势识别模型。之后，它处理一个图像（这里的image变量应该是你的图像数据），并打印出识别到的手部标记信息。
总结 MediaPipe提供了一个强大而灵活的框架，用于构建和部署复杂的媒体处理应用。通过其跨平台支持、预构建模型和高效的图形处理系统，开发者可以更容易地创建出引人注目的实时媒体应用。无论你是在构建一个互动游戏、社交媒体滤镜还是高级监控系统，MediaPipe都是一个值得考虑的工具。
MediaPipe的开放源代码和活跃的社区也意味着它将继续发展和改进，为开发者提供更多的机会和可能性。如果你对开发有关媒体处理的应用感兴趣，那么现在就是时候开始探索MediaPipe了。</content></entry><entry><title>PySide VS PyQT</title><url>https://www.d-j.fun/post/notes/2024/0305_pyside_vs_pyqt/</url><categories><category>notes</category></categories><tags><tag>pyside</tag><tag>pyqt</tag></tags><content type="html"> PySide 与 PyQt 的比较 在Python界，当提到跨平台GUI（图形用户界面）应用开发时，PySide和PyQt是两个非常流行的库。它们都是Qt库的Python绑定，Qt是一款跨平台的C++图形用户界面应用程序框架，广泛应用于开发具有图形用户界面的软件程序。尽管PySide和PyQt有很多相似之处，但它们在许可、API兼容性、社区支持等方面存在差异。本文旨在比较这两个库，帮助开发者选择最适合自己项目的工具。
许可和所有权 PyQt：PyQt最初由Riverbank Computing开发。它主要在GPL和商业许可证下提供，这意味着如果你打算开发并分发基于PyQt的应用程序，你的应用程序需要开源（如果选择GPL许可）或者你需要购买商业许可。这对于一些希望保留源代码或在闭源项目中使用Qt的公司来说可能是一个限制。
PySide：相比之下，PySide（也称为Qt for Python）是由Qt Company官方支持的Python绑定，它在LGPL许可下提供。LGPL许可允许开发者在不开源其应用源代码的情况下，使用PySide进行商业和非商业项目开发，只要他们遵守LGPL许可的其他要求。这使得PySide对于需要更灵活许可模型的项目更加吸引人。
API兼容性 PyQt和PySide都提供了对Qt应用程序框架的广泛支持，包括对Qt5和Qt6的支持。两者的API在很大程度上是兼容的，这意味着从一个库迁移到另一个库通常只需要修改少量代码。然而，存在一些细微的差异，特别是在信号和槽机制的实现细节上。PyQt5以及之后的版本引入了一种新的信号和槽语法，该语法更加Pythonic，但这也意味着PySide和PyQt在某些用法上可能有所不同。 性能和社区支持 性能：就性能而言，PySide和PyQt之间没有显著差异。两者都能提供快速响应的GUI应用程序。性能更多地取决于Qt本身以及应用程序的设计和实现。
社区支持：由于PyQt的历史更长，它拥有一个庞大的用户基础和丰富的学习资源。然而，随着PySide2（Qt for Python）的发布和Qt Company的官方支持，PySide的社区也在迅速增长。在线论坛、开发者指南和教程都可用于两个库，帮助开发者解决开发过程中遇到的问题。
开发体验 工具和IDE集成：PyQt和PySide都与多种开发工具和IDE（如PyCharm、VS Code等）良好集成，提供了可视化设计工具（如Qt Designer）来帮助创建GUI界面。这些工具可以生成Python代码，使得界面设计更加直观和高效。 结论 选择PySide还是PyQt，主要取决于你的项目需求、许可考虑和个人偏好。如果你需要或偏好使用LGPL许可，或者你的项目需要Qt Company的直接支持，那么PySide可能是更好的选择。相反，如果你已经在使用PyQt，或者你发现PyQt的社区资源对你的项目更有帮助，那么继续使用PyQt也是合理的选择。无论选择哪个，它们都是创建现代、跨平台Python GUI应用程序的强大工具。</content></entry><entry><title>TCP网络编程概览</title><url>https://www.d-j.fun/post/notes/2023/0216_tcp_all_related/</url><categories><category>notes</category></categories><tags/><content type="html"> 端口号根据范围分为三种
Well-Known Ports（即公认端口号） 它是一些众人皆知著名的端口号，这些端口号固定分配给一些服务，我们上面提到的 HTTP 服务、 FTP服务等都属于这一类。知名端口号的范围是：0-1023
Registered Ports（即注册端口） 它是不可以动态调整的端口段，这些端口没有明确定义服务哪些特定的对象。不同的程序可以根据自己的需要自己定义，注册端口号的范围是：1024-49151
Dynamic, private or ephemeral ports（即动态、私有或临时端口号） 顾名思义，这些端口号是不可以注册的，这一段的端口被用作一些私人的或者定制化的服务，当然也可以用来做动态端口服务，这一段的范围是：49152–65535
TCP 和 UDP 可以同时绑定相同的端口吗？ 答案：可以的。
在数据链路层中，通过 MAC 地址来寻找局域网中的主机。在网际层中，通过 IP 地址来寻找网络中互连的主机或路由器。在传输层中，需要通过端口进行寻址，来识别同一计算机中同时通信的不同应用程序。
所以，传输层的「端口号」的作用，是为了区分同一个主机上不同应用程序的数据包。
传输层有两个传输协议分别是 TCP 和 UDP，在内核中是两个完全独立的软件模块。
当主机收到数据包后，可以在 IP 包头的「协议号」字段知道该数据包是 TCP/UDP，所以可以根据这个信息确定送给哪个模块（TCP/UDP）处理，送给 TCP/UDP 模块的报文根据「端口号」确定送给哪个应用程序处理。</content></entry><entry><title>mysql执行计划命令explain的输出解释</title><url>https://www.d-j.fun/post/notes/2023/0207_mysql_explain_related/</url><categories><category>notes</category></categories><tags><tag>mysql</tag></tags><content type="html"> 平时所说mysql优化，最常用的手段就是通过执行explain（desc） 具体的sql，获取到mysql执行计划的一些信息，根据平时积累的经验来判别sql的执行效率。使用explain工具的前提，需要积累很多mysql相关基础知识。否则即使了解了输出什么意思，也没办法得出什么有用的结论。本片文章就explain输出的几个字段做出解释。
必需基础知识 索引结构和存储 仅就innodb索引来说明，innodb是由B+树组织索引，下面就innodb索引分类简单解释：
主键索引 叶子节点存储的是整行数据 非叶子节点存储主键数据 二级索引 或普通索引 叶子节点存储的是主键id 非叶子节点存储指定字段数据 mysql 组成结构 mysql由连接器（管理连接和权限验证）、分析器（词法分析、语法分析）、优化器（索引选择、执行顺序）、执行器（调用存储引擎、得出结果并返回）组成，而存储引擎作为插件式被执行器调用。 一个个客户端要执行sql，首先要创建连接，然后经过分析器分析判断是否是合法的sql，如果合法得出语法树，交给优化器。优化器根据内嵌的规则进行索引选择、子表执行顺序等，输出执行计划。explain得出的便是执行计划相关的信息。
expain输出字段 type 执行计划类型 type是根据执行计划查询特征来分类，一定程度可以代表一次查询的性能指标。通过该字段可以判断出是否走索引（all），以什么形式（range）。 主要包括（根据性能排序）：
all 全表扫描，没有可用的索引，只能扫描主键索引。 index 遍历索引树，索引树叶子节点只包含主键，数据量更小 range 在INDEX的基础上，范围扫描，比如between 大于小于 REF 非唯一索引扫描 EQ_REF 唯一索引扫描 const system 常量查询，比如 查询主键 null 执行时不需要访问表和索引 可以发现执行计划类型主要根据是否使用索引、使用什么类型索引以及怎么使用索引来分类的。
key len的计算 key_len 顾名思义就是所使用到的 索引元组项的长度。比如 create index name_age_index on table (name, age);，创建name、age为联合索引，name为varchar(191)， age为int， 执行name=&amp;quot;abc&amp;quot; and age = 3， 便会发现key_len的值为 770。
首先 字符串的计算规则：
各个字符集下，每个字符存储的长度为 latin 1个字节，gbk 2个字节，utf8 3个字节，utf8mb4 4个字节，（因为utf8只有三个字节，所以会损失一些字符，建议使用4个字节的utf8mb4） 如果是varchar会比原来的长度+2个字节，记录字段存储内容的长度。 如果允许为null，再增加一个字节记录是否为null 上面的例子 name为varchar 191 * 4 + 2 = 766
其他类型的计算规则：
int 4个字节、mediumint 3个字节、smallint 2个字节、tinyint 1个字节 datetime 8、timestamp 4、time 3、date 3、year 1 如果允许为null，再增加一个字节记录是否为null 上面的例子 age为int 不允许为null 所以为4
重要的extra字段 Using filesort 一个sql的排序过程 分为 全字段排序和rowid排序，排序默认会选择再sort buffer（mysql专门为排序创建的缓存区）进行排序，如果buffer空间不够，会选择利用文件进行合并排序（即分为一定数量的文件，各个文件分别排序，再组合在一起）。很明显如果出现using filesort 代表是可以优化的：
考虑业务需求是不是可以不再这里排序 尝试通过调整buffer空间 如果必须进行文件排序，那么是否可以通过覆盖索引减少回表次数 Using where 参考MySQL组成结构，可以发现 执行器输入MySQL，而存储引擎是插件式组件。 如果where条件中有不在索引元组字段中的字段，比如索引元组为（age, name）， where中包含sex的查询。这种情况下，该索引已经无法满足本次查询，必须回表获取完整的数据，然后在MySQL服务器中做二次过滤。 explain select name from a where name = &amp;quot;jerry&amp;quot; and sex =1 ;
Using index 相对应的，完全可以通过索引覆盖查询需求
Using Index Condition 先通过索引里的数据进行筛选，一般情况下索引会根据索引项找到所有相关的数据，是不会进行任何过滤的，比如 name = &amp;ldquo;jerry&amp;rdquo; and age &amp;gt; 3, 存储引擎在没有索引下推icp的情况下是直接返回所有name = jerry的数据，而在MySQL服务器端进行age &amp;gt; 3的过滤，这种情况下会增加回表的记录个数。
案例分析 explain select sex from a where name = &amp;quot;jerry&amp;quot; and age &amp;gt; 1 ; explain select name from a where name = &amp;quot;jerry&amp;quot; and age &amp;gt; 1 ; 1 在extra中显示的是Using Index Condition，2 显示的是 Using where; Using index
区别在于2输入覆盖索引，不需要回表查询：
Using index 代表覆盖索引 Using where 在这里代表 进行了索引的过滤 总结 MySQL优化可以通过explain的输出窥见一斑，但是更深次的原因以及后续需要采用的优化手段，却需要大量的mysql关于索引、执行计划以及其细节才能做到。</content></entry><entry><title>kind上安装kubeflow（国内镜像源）</title><url>https://www.d-j.fun/post/technical/2022/0831_kubeflow_on_kind_cn/</url><categories><category>technical</category></categories><tags><tag>运维</tag></tags><content type="html"> kubeflow 机器学习的工具集，包含Notebook（交互实验）、AutoML（自动化处理）、Pipeline（流水线）、Serverless（部署）。
由于是google自己的产品开源，很多组建依赖于google，即国内不友好。这里推荐一个国内的解决方案（https://github.com/shikanon/kubeflow-manifests/）。
kind 工具安装集群 安装kind kind是搭建本地k8s集群的工具，主要用来测试k8s，安装kind可以使用release binary方式：
curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.14.0/kind-linux-amd64 chmod +x ./kind sudo mv ./kind /usr/local/bin/kind 创建集群 kind create cluster --config=kind/kind-config.yaml --name=kubeflow --image=kindest/node:v1.19.11 先拉取shikanon/kubeflow-manifests，在代码根目录执行该语句，config对应文件kind/kind-config.yaml便是在这里
image版本很重要，由于这里是kubeflow1.3，测试来看最新版本是不可取的，1.19.4以下创建集群不成功。否则会出现CrashLoopBackOff的pod，提示莫名的问题。如果有类似问题，先尝试切换版本。
kind delete cluster --name kubeflow 下载新版本的时候，清除资源可以使用该语句。如果硬盘资源有限可以使用docker rmi删除多余镜像。
常规排查问题手段
kubectl get pods -A kubectl logs {pod} -n kubeflow kubectl describe pod|describe kubectl rollout restart deploy {deploy} -n kubeflow # 重启deploy kubectl get secret mysql-secret -n kubeflow -o jsonpath=&amp;#39;{.data}&amp;#39; # 获取secret 调试部分资源可截取部分yaml文件，先delete后apply
解决方案简介 该解决方案将所有的清单中的镜像地址替换为了阿里云，并做了辅助脚本以及个性化设置。如果有定制化需求，可以修改对应的yaml文件，自行apply
install.py文件 定义了两个方法install和patchInstall，主要是迭代读取对应目录下的yaml文件，挨个apply（patch会先delete）
python install.py 执行完该语句，等待20分钟左右，如果都running状态即成功，直接方案30000端口即可。
manifest1.3目录 kubeflow所有依赖组件的yaml文件组合。install方法既是读取该文件下的所有文件，挨个apply。安装过程中遇到问题，可以回溯到该目录，查看对应的文件。
patch目录 一些自定义化设置，包括一些问题的fixed。比如auth.yaml文件添加修改用户，同时可以按照自己需求修改默认的命名空间，替换kubeflow-admin-example-com为自己的名字，记得必须以kubeflow-为前缀，否则会在前端显示没有namespace。
后续问题 run创建pod的时候describe发现问题 MountVolume.SetUp failed for volume &amp;#34;mlpipeline-minio-artifact&amp;#34; : secret &amp;#34;mlpipeline-minio-artifact&amp;#34; not found 命名空间的问题，将命名空间kubeflow替换为自己的命名空间（我修改为了kubeflow-admin） 解决办法
kubectl get secret mlpipeline-minio-artifact --namespace=kubeflow -o yaml | sed &amp;#39;s/namespace: kubeflow/namespace: kubeflow-admin/&amp;#39; | kubectl create -f - pipeline运行报错 这个错误是由于 kind 集群创建的 k8s 集群容器运行时用的containerd，而workflow默认的pipeline执行器是docker，因此有些特性不兼容。如果你的 k8s 集群是自己基于docker runtime 搭建的，可以将patch/workflow-controller.yaml的containerRuntimeExecutor改为docker，这样就不存在兼容性问题了。 详见 方案文档
notebook pod 无root权限 jupyter-web-app pod是负责管理 notebook-server接口的提供者，使用flask实现的，并用/usr/local/bin/gunicorn向外提供服务。 kubectl exec -it jupyter-web-app-deployment-{序号} -n kubeflow -- bash可以连接到pod中的容器中 编辑容器中apps/common/yaml/notebook_template.yaml，添加
securityContext: runAsUser: 0 runAsGroup: 0 fsGroup: 0 然后重新添加一个notebook server，通过terminal进去，便可以发现是root用户，可以通过apt安装自己想要的组建。 我是安装tfx，需要gcc g++， 通过执行apt install gcc g++便解决了。
kfp client 403 Internal error: Unauthenticated: Request header error: there is no user identity header.: Request header error: there is no user identity header 这些报错是请求的时候缺少header头，可以通过附加认证策略来解决
apiVersion: security.istio.io/v1beta1 kind: AuthorizationPolicy metadata: name: bind-ml-pipeline-nb-kubeflow-admin namespace: kubeflow spec: selector: matchLabels: app: ml-pipeline rules: - from: - source: principals: [&amp;#34;cluster.local/ns/kubeflow-admin/sa/default-editor&amp;#34;] -- apiVersion: networking.istio.io/v1alpha3 kind: EnvoyFilter metadata: name: add-header namespace: kubeflow-admin spec: configPatches: - applyTo: VIRTUAL_HOST match: context: SIDECAR_OUTBOUND routeConfiguration: vhost: name: ml-pipeline.kubeflow.svc.cluster.local:8888 route: name: default patch: operation: MERGE value: request_headers_to_add: - append: true header: key: kubeflow-userid value: admin@example.com workloadSelector: labels: notebook-name: test999 如果将workloadSelector去掉，那么会应用于所有的notebook 详见 issue
kfp cli命令提示 token文件缺失 Failed to read a token from file '/var/run/secrets/kubeflow/pipelines/token'
通过describe pod会发现/run/secrets/kubernetes.io/serviceaccount/token这个是真是的token文件，所以只要 export KF_PIPELINES_SA_TOKEN_PATH=token文件即可， 或者配置poddefault配置，详见 issue
client = kfp.Client() client.create_run_from_pipeline_package(pipeline_file=&amp;#39;{yaml文件}&amp;#39;, arguments={},experiment_name=&amp;#34;{实验名称}&amp;#34;, namespace=&amp;#34;{命名空间}&amp;#34;) 执行结果会列出对应的 run 的链接。
引用 https://github.com/shikanon/kubeflow-manifests/ https://hub.docker.com/r/kindest/node/tags?page=1&amp;amp;name=v1.19 kind镜像版本 https://github.com/argoproj/argo-workflows argo workflow https://github.com/kubeflow/pipelines kubeflow</content></entry><entry><title>Laravel 消息队列源码分析</title><url>https://www.d-j.fun/post/technical/2022/0712_laravel_queue_work_source_analysis/</url><categories><category>technical</category></categories><tags><tag>laravel</tag></tags><content type="html"> 消息队列处理 queue:work 对应 \Illuminate\Queue\Console\WorkCommand
依赖注入\Illuminate\Queue\Worker和 \Illuminate\Contracts\Cache\Repository 如果不带强制参数--force，系统处于下线状态，并且带once参数，那么调用work的sleep方法 listenForEvents 注册一个job生命周期事件 获取connection和queue 开始运行work 需要注意\Illuminate\Queue\WorkerOptions在收集参数的时候，设置了默认值 根据是否带once参数来决定运行哪个方法runNextJob和daemon extension_loaded('pcntl') 如果有该模块，那么监听信号 queue:restart会记录 key为illuminate:queue:restart缓存 循环一开始判断是否处理任务、暂停、退出 获取一个job，同时注册timeout，防止job会被阻塞住 如果有job，那么运行job，没有job就sleep，WorkOptions中指定的时间。 $jobsProcessed标示一个work目前执行了多少个job，超过配置中的maxjob就停止 重置Timout处理器，检查是否有必要停止work。 任务执行 \Illuminate\Queue\Worker::process 任务执行函数
抛出JobProcessing事件 检查任务是否超过最大运行次数限制，超过了就标示错误，并从reserved队列中删除，发送after事件 获取要执行的job，这里得到的job是RedisJob，封装过一层的。pop是在RedisQueue类上封装的 setJobInstanceIfNecessary先设置job实例到command（你自己的处理类），InteractsWithQueue使用这个trait的会有该步操作 dispatchThroughMiddleware 执行之前先走中间件，然后调用handle方法 任务执行异常 \Illuminate\Queue\Worker::handleJobException
如果任务执行异常而且没有标示为失败，检查重拾次数，最大异常数，抛出JobExceptionOccurred事件 如果可以继续，那么计算calculateBackoff，得到重试秒数</content></entry><entry><title>Laravel FileSystem源码分析</title><url>https://www.d-j.fun/post/technical/2022/0626_laravel_filesystem_source_analysis/</url><categories><category>technical</category></categories><tags><tag>laravel</tag></tags><content type="html"> Facade 入手 Laravel Facade模式实现方式是：所有的Facade（比如Storage）都需要继承虚拟类Facade。虚拟类Facade提供了一个__callStatic方法，来承接所有的方法调用。
该方法提供的作用包括：
获取具体Facade对应的实例。子类实现的getFacadeAccessor方法提供 调用实例对应方法。__callStatic方法参数提供。 而Storage Facade对应的实例是FilesystemManager对象，是通过FilesystemServiceProvider注册到容器里面的。
适配器 Laravel在多处地方使用到适配器，比如 database, auth, filesystem。不同适配器都会对应一个Manager后缀的管理器，比如FilesystemManager对应着filesystem。
管理器主要功能：
创建不同的driver 注册新driver 获取driver Storage通过调用disk（或drive、cloud对应配置filesystems.cloud），获取（如果不存在创建）对应driver实例。
如果想实现自己的driver，可以通过FilesystemManager的extend方法，比如你想使用aliyun oss云存储，那么可以
$this-&amp;gt;app-&amp;gt;make(&amp;#39;filesystem&amp;#39;)-&amp;gt;extend(&amp;#39;aliyun&amp;#39;, function ($app, array $config) { // 返回根据config创建的实例即可 }); 如果返回的实例实现了League\Flysystem\FilesystemInterface接口，那么就使用FilesystemAdapter统一包装一下，如果没有，直接返回driver实例，给调用方。而所谓的包装，大部分作用是为了和laravel本身的调用接口进行统一，做了一层代理转发。如果driver本身实例存在FilesystemAdapter没有的方法，还是会溯源调用driver方法。
如果想实现自己的driver，那这里的driver对应的不应该是aliyun官方client实例，而应该实现自己的adapter做一层适配。
调用举例 本身存在方法 getTemporaryUrl Storage Facade调用disk方法，获取到driver实例 driver实例满足FilesystemInterface，被FilesystemAdapter包装 FilesystemAdapter中的方法temporaryUrl，接受调用 调用driver本身的getTemporaryUrl的方法。 调用driver本身方法 Storage Facade调用disk方法，获取到driver实例 driver实例满足FilesystemInterface，被FilesystemAdapter包装 FilesystemAdapter中的方法__call，接受调用 调用driver本身的方法。 总结 Facade找到Manager实例，Manager创建driver，并调用driver对应的方法。Manager同时负责扩展driver，用Adapter包装driver，来统一调用接口。</content></entry><entry><title>在Kubernetes上搭建私有PyPi仓库</title><url>https://www.d-j.fun/post/notes/2022/05223_pypi_server_on_kubernetes/</url><categories><category>notes</category></categories><tags><tag>aws</tag></tags><content type="html"> 公共PyPi仓库上我们平时工作中经常使用的，可以解决我们大部分情况下分发包的需求。但公司的项目往往需要搭建一个私有的PyPi仓库来保证代码安全，以及防止私密信息无意泄漏。
本篇文章展示了在aws eks（即k8s）上搭建一个私有PyPi仓库的完整过程，并且编写一个demo包走完上传安装的流程。
pypiserver pypiserver库，pypi的服务器实现，通过pip install pypiserver 安装。直接pypi-server便可以启动起来，详细的参数参考 pypiserver文档, 常用参数：
-P /pypi-server/auth/.htpasswd 该参数指定的apache htpasswd file，包含用来验证的用户名密码 a update,download,list 逗号分隔，指定需要验证的动作 p 端口号，默认8080 ./packages 指定包存储位置，可以指定多个目录 pypi-server -v -P /pypi-server/auth/.htpasswd -a update,download,list ./packages
其中.htpasswd是htpasswd -b -c /pypi-server/auth/.htpasswd user pass生成
使用PyPi仓库 Demo包 创建setup.py文件
import setuptools setuptools.setup( name=&amp;#34;demo&amp;#34;, version=&amp;#34;0.0.1&amp;#34;, packages=setuptools.find_packages(), ) 另外创建一个demo.py文件
def demo(): print(&amp;#34;this is a demo&amp;#34;) 打包上传 python setup.py check 检测文件是否正确 python setup.py sdist 打包 twine upload -r kube dist/* 上传打包好的文件 其中kube可以直接使用http://user:pass@127.0.0.1:8080, 或者交互式输入密码。
也可以在cat ~/.pypirc文件中配置：
[distutils] index-servers = kube [kube] repository = http://127.0.0.1:8080 username = user password = pass 浏览器打开http://127.0.0.1:8080，便可以看到刚才上传的包文件。
Kubernetes搭建 Dockerfile FROM python:3.8-alpine RUN apk update update \ &amp;amp;&amp;amp; apk add apache2-utils \ &amp;amp;&amp;amp; apk add bash \ &amp;amp;&amp;amp; mkdir /pypi-server WORKDIR /pypi-server RUN mkdir packages &amp;amp;&amp;amp; mkdir auth RUN python3 -m pip install pypiserver passlib COPY ./docker_entry.sh /pypi-server RUN chmod +rx ./docker_entry.sh ENTRYPOINT [&amp;#34;/pypi-server/docker_entry.sh&amp;#34;] EXPOSE 8080 同目录文件docker_entry.sh
#!/bin/bash htpasswd -b -c /pypi-server/auth/.htpasswd $PYPI_USER $PYPI_PASS pypi-server -v -P /pypi-server/auth/.htpasswd -a update,download,list ./packages 执行如下步骤：
1 docker build -t pypiserver . 编译镜像 2 docker tag pypiserver:latest user/pypiserver:v0.0.1 3 docker push user/pypiserver:v0.0.1 推送镜像到官方仓库 deployment apiVersion: apps/v1 kind: Deployment metadata: name: pypiserver labels: app: pypiserver spec: // ... spec: nodeSelector: 标签对，因为使用hostPath来持久化，所以必须使用nodeSelector，指定pod所在主机 containers: - name: pypiserver image: user/pypiserver:v0.0.1 imagePullPolicy: Always env: - name: PYPI_USER value: user - name: PYPI_PASS value: pass volumeMounts: - mountPath: &amp;#39;/pypi-server/packages&amp;#39; name: pypi-packages ports: - containerPort: 8080 protocol: TCP resources: requests: cpu: 200m memory: 512Mi volumes: - name: pypi-packages hostPath: path: /data/ secrets 用户名密码可以走secrets spec，对应yaml文件
apiVersion: v1 kind: Secret metadata: name: pypisecret type: Opaque stringData: username: user password: pass 简短说明 PYPI_USER｜PYPI_PASS这两个环境变量，参考docker_entry.sh文件，是用来生成htpasswd文件 由于pod或者说容器重新启动后，上传的包就不存在了，所以必须持久化，这里为了简单是用了 hostPath，可以根据自己情况选择具体持久化类型 gitlab上搭建 gitlab本身是具有各种语言共享库的功能：
Composer Conan Go Maven NPM NuGet PyPI Generic packages 使用起来也很简单，只需要创建验证信息即可：
创建个人Token，scope设置为api 设置 ~/.pypirc: [gitlab] repository = https://gitlab.example.com/api/v4/projects/&amp;lt;project_id&amp;gt;/packages/pypi # https://gitlab.example.com/api/v4/groups/&amp;lt;group_id&amp;gt;/-/packages/pypi username = &amp;lt;your_personal_access_token_name&amp;gt; password = &amp;lt;your_personal_access_token&amp;gt; 使用twine发布 python3 -m twine upload --repository gitlab dist/* k8s搭建私有PyPi仓库
gitlab PyPi仓库</content></entry><entry><title>基于presto-go-client分析database/sql</title><url>https://www.d-j.fun/post/notes/2022/0522_analyze_database_sql_based_on_presto_go_client/</url><categories><category>notes</category></categories><tags/><content type="html"> presto-go-client 是一个golang的Presto客户端，基于golang database/sql通用接口实现，方便开发者快速方便使用。这也是定义接口协议的意义体现。
使用 db, err := sql.Open(&amp;#34;presto&amp;#34;, &amp;#34;http://localhost:9&amp;#34;) if err != nil { t.Fatal(err) } defer db.Close() rows, err := db.Query(&amp;#34;select 1 &amp;#34;, sql.Named(&amp;#34;X-Presto-User&amp;#34;, &amp;#34;root&amp;#34;)) if err != nil { t.Fatal(err) } var testId string for rows.Next() { err := rows.Scan(&amp;amp;testId) if err != nil { t.Fatal(err) } } 由于实现了database/sql通用数据库操作接口，使用方式和其他数据库一样。
presto实现 注册Driver sql.Register(&amp;#34;presto&amp;#34;, &amp;amp;sqldriver{}) 规范操作
sql.Open 初始化DB Struct： OpenDB不验证参数，但是不真正创建和数据库的链接。数据库链接的建立时机是在第一次真正需要建立的时候。
保存Connector：链接器，保存链接的信息，以及driver（这里是指presto，即实现了driver.Driver接口的结构体）指针 初始化openerCh：开启器channel，是一个默认1000000长度的struct{} chan 链接请求记录map初始化 可取消context初始化 sql.DB不是数据库链接，而是数据库的一种虚拟，它可以做很重要的任务：打开关闭链接；链接池管理。实现并发访问数据存储的功能。
sql.DB被设计为持久存在的，不要频繁的开启和关闭。通用的方案是为每一个数据库创建一个全局、或者方便传递的对象，保持对象存在，直到程序结束。
创建链接 链接建立是在query函数db.query调用的时候， db.conn完成这些操作：
判断数据库是否关闭（db.closed）, DB的Close函数，会赋值该开关为true 检测context是否Done，如果Done会返回ctx.Err()，这里也是QueryContext中context的实现 取db.freeConn中的最后一个链接，或者调用db.connector.Connect重新创建一个或多个(给openerCh传递信号) 链接池是一个管理空间链接，给请求复用链接的机制。database/sql的实现： 当某个goroutine需要链接的时候，先查看空间链接数组是否有可用，如果有，直接返回；如果没有，则需要判断当前开启的链接数是否达到最大值，如果是阻塞goroutine。否则创建一个链接。
链接池相关操作对应函数：
DB.conn：获取链接 driverConn.releaseConn：调用DB.PutConn释放连接 DB.PutConn：进行一些处理，调用DB.putConnDBLocked。 DB.putConnDBLocked：丢弃连接、返回连接给等待的协程或放到空闲队列。 DB.maybeOpenNewConnections：根据目前的连接请求数量和目前还能打开的连接数量判断是否发送创建连接信号给协程connectionOpener。 DB.connectionOpener：负责异步创建连接。 presto真正意义上的链接建立就在这一步，由于presto是一个http client，所以建立链接的过程很简单，只需要初始化以及验证即可：
解析dsn kerberos检测 增加了custom_client的支持 初始化http headers c := &amp;amp;Conn{ baseURL: prestoURL.Scheme + &amp;#34;://&amp;#34; + prestoURL.Host, httpClient: *httpClient, httpHeaders: make(http.Header), kerberosClient: kerberosClient, kerberosEnabled: kerberosEnabled, } 查询执行 查询执行对应函数：
QueryContext 接受sql、参数，获取数据库链接，调用Driver的对应方法查询数据 Query是QueryContext不支持context的版本 Exec 修改数据，注意如果不关心返回结果，推荐使用Exec，因为Query会保留数据库链接，直到sql.Rows关闭。可能存在维度数据，导致链接不被释放。 var testId string for rows.Next() { err := rows.Scan(&amp;amp;testId) if err != nil { t.Fatal(err) } } Presto在这里的实现，又可以被借鉴的地方。它利用NamedArgs来设置http headers。
检查args是否有特定的参数，设置header 发送post请求给/v1/statement 解析为driverRows 如果nextUri非空，迭代rows.fetch，直到获取到最后的数据 POST http://127.0.0.1:8081/v1/statement Host: 127.0.0.1:8081 User-Agent: Go-http-client/1.1 X-Presto-Catalog: hive X-Presto-User: test Accept-Encoding: gzip select 1 next &amp;amp;&amp;amp; scan next next函数结果指示下一行是否可用：true 可用，false 数据集耗尽(io.EOF)，或者中间出现错误。 注意 rows的关闭这个时候是调用房的责任，当rows开启状态的时候， 数据库链接是忙碌状态。如果忘记close，或者rows存在时间较长，是可能出现链接泄漏的情况的。
func (qr *driverRows) Next(dest []driver.Value) error { // ... if qr.columns == nil || qr.rowindex &amp;gt;= len(qr.data) { if qr.nextURI == &amp;#34;&amp;#34; { qr.err = io.EOF return qr.err } if err := qr.fetch(true); err != nil { return err } } // ... for i, v := range qr.coltype { vv, err := v.ConvertValue(qr.data[qr.rowindex][i]) // ... dest[i] = vv } qr.rowindex++ return nil } 以上是presto的next实现:
columns如果为初始化为nil，代表没有数据或执行错误 rowindex大于等于时机的数据：nextUri不为空代表有后续数据，调用fetch继续请求 正常情况挨个转化数据到dest，提供给scan使用 scan for i, sv := range rs.lastcols { err := convertAssignRows(dest[i], sv, rs) if err != nil { return fmt.Errorf(`sql: Scan error on column index %d, name %q: %w`, i, rs.rowsi.Columns()[i], err) } } 注意lastcols，就是next的参数dest。scan辅助你对结果的操作，帮你自己转换变量，迭代数据集的每一行。
// 仅需一行代码 err = db.QueryRow(&amp;#34;select name from users where id = ?&amp;#34;, 1).Scan(&amp;amp;name) Driver的意义 Driver是真正执行任务的代码，像presto-go-client，所有和presto链接建立、数据解析等操作都由该库完成。而database/sql，定义了整个数据库操作流程，以及所有相关的结构定义。presto-go-client只需要按照定义实现出来，即可以被使用。
这样的设计，使你使用多种数据库（对应多个driver），却是相同的代码，相同的流程。也方便初次使用某个数据库的时候，能够快速接入。也方便driver开发者，无需关心管理上的细节，只需要实现就好。
但是也限制了自己任务发挥的能力，有得也有失。像presto提供的是一个http协议的接口，参数、验证信息都是依赖http协议，一些附加信息也无法突破next/scan操作的限制。
presto文档
Golang database/sql与go-sql-driver源码阅读笔记
database/sql工作机制</content></entry><entry><title>如何使用openssl加密大文件</title><url>https://www.d-j.fun/post/technical/2022/0518_how_to_encrypt_large_file_openssl/</url><categories><category>technical</category></categories><tags><tag>Linux命令</tag></tags><content type="html"> 强大的openssl命令 openssl，一个linux命令，囊括了主要的密码算法、常用的密钥和证书封装管理功能，提供丰富应用程序测试等。只要涉及到加密、hash、证书等相关的需求，都可以使用openssl进行解决。
本文涉及到的相关只是，只是服务于本文 加密较大文件方案，只涉及到openssl的应用的很小一部分：
openssl enc 执行对称加密算法。具体支持的算法通过openssl enc -h命令查看。其中in &amp;amp; out参数分别代表输入和输出。当然也接受标准输出管道作为参数。e &amp;amp; d参数分别代表加密、解密。 openssl genpkey 生成私钥，algorithm指定具体的算法 openssl rsautl执行非对称加密 加密文件 生成大文件 mkfile -n 1g test.txt ### -rw------- 1 eight staff 1.0G May 19 01:19 test.txt 使用命令生成一个1g的大文件，用来观察加密效果、以及测试加密大文件的性能。
echo &amp;#34;\n观察加密效果，这是机密数据&amp;#34; &amp;gt;&amp;gt; test.txt 加密算法的选择 对称加密算法AES（Advanced Encryption Standard）支持128、192、256字节的密钥。AES使用简单的代数运算，使用相同的方式加密每block的数据。这种特质更适合加密大文件。但正是它的简单，造成在抗暴力破解方面，不如RSA。
非对称加密，使用一对key来加解密电子信息。RSA (Rivest–Shamir–Adleman)是我们经常使用的非对称加密，经常用于ssl、ssh等领域。它机密每block都使用不同的方式，比其他机密更安全。但是也造成在加密较大文件的时候，性能方面不理想。
结合AES和RSA 生成一个keyfile，作为AES加密的密钥文件。
openssl rand 256 &amp;gt; pwd.key AES加密test.txt，完成不到5s。
&amp;gt;&amp;gt; time openssl enc -in test.txt -out test.txt.enc -e -aes256 -k pwd.key openssl enc -in test.txt -out test.txt.enc -e -aes256 -k pwd.key 1.37s user 1.74s system 79% cpu 3.918 total AES解密test.txt.enc, 完成不到3s
&amp;gt;&amp;gt; time openssl enc -in test.txt.enc -out test.txt.decrypt -d -aes256 -k pwd.key openssl enc -in test.txt.enc -out test.txt.decrypt -d -aes256 -k pwd.key 0.51s user 1.99s system 72% cpu 3.434 total 举例：
openssl enc -aes-256-cbc [-pass pass:密码] -in 源文件 -out 加密后的文件 openssl enc -d -aes-256-cbc -in 加密后的文件 -out 解密文件 RSA相关操作
生成私钥openssl genpkey -algorithm RSA -out private_key.pem -pkeyopt rsa_keygen_bits:2048 对应公钥openssl rsa -pubout -in private_key.pem -out public_key.pem 加密密钥文件
openssl rsautl -encrypt -inkey public_key.pem -pubin -in pwd.key -out pwd.key.enc 解密密钥文件
openssl rsautl -decrypt -inkey private_key.pem -in pwd.key.enc -out pwd.key 总结 AES适合机密大文件、RSA加密方法安全。总体思路：使用RSA加密密钥文件，然后使用AES结合密钥文件加密大文件。
这样相结合大文件，首先需要RSA加密时候对应的密钥对的另一个，解开密钥文件，然后使用密钥文件再解开AES。其实仔细分析ssl协议会发现，使用了类似的方案。
总体方案：
生成公私钥 生成密钥文件 RSA加密密钥文件 AES加密大文件</content></entry><entry><title>xdebug3调试原理分析及配置迁移</title><url>https://www.d-j.fun/post/technical/2022/0515_how_to_debug_with_xdebug3_and_mechanism/</url><categories><category>technical</category></categories><tags/><content type="html"> xdebug3相对于2，配置方式修改了很多，在配置的时候会给出相应提示。本文使用较大篇幅分析xdebug的调试原理，以及简单的调试。针对结合PHPSTROM进行php调试也有简单涉及。
xdebug3常用配置迁移 开启方式，不再是0，1的简单设置，而是使用mode来指定xdebug处于什么模式。方便于xdebug只是加载实际需要的特性。
xdebug.mode=debug # 开启Step Debugging # 其他值off 关闭，develop 开启开发辅助，coverage 开启代码覆盖率分析，gcstats 开启垃圾收集状态统计， profile 开启分析，trace 开启函数追踪特性。 # 可以使用 xdebug_info() 查看配置 激活命令行调试export XDEBUG_CONFIG=idekey=yourname 替换为 export XDEBUG_SESSION=xdebug_is_great
自动开启调试xdebug.remote_autostart 替换为 xdebug.start_with_request
调试端口默认不再是9000，而是9003（更合理，9000往往会和fpm冲突）
调试原理 IDE集成遵循BGDP的xdebug插件，监听9000端口，监听服务端发过来的debug信息 浏览器发送带了XDEBUG_SESSION_START参数 后端php收到该请求（开启xdebug），xdebug向来源客户端9000端口发送debug请求 客户端相应这个请求，debug就建立了 后端php已经准备好后，一行行执行代码，每执行一行，就交给xdebug处理一下 xdebug处理过程，会暂停代码执行，向客户端发送代码的执行情况，等待客户端的操作 上文提到的客户端、IDE，这些指代监听9000端口这一方。而后端、服务端是指php，真正执行代码一方。 现实场景有可能是：客户端（浏览器）发起http请求，nginx接受http请求，转发给fpm（服务端），执行PHP代码。
代码分析 演示代码 &amp;lt;?php echo &amp;#39;Hello world&amp;#39;; // 1 $a = 20; // 2 $b = 40; // 3 $c = $a + $b; // 4 echo $c.&amp;#39;&amp;#39;; // 5 echo &amp;#39;Hello world&amp;#39;; // 6 方便后面分析，后面注释代表行号。
简单检测 按照调试原理，可以看出，当开启了
xdebug.mode=debug xdebug.start_with_request 执行php代码php index.php, 执行方即php, 会发送9003的请求给客户端。客户端如果依赖于ide（如phpstorm），开启右上角监听按钮即可。为了分析具体情况，可以用nc模拟监听9003端口
nc -lv 127.0.0.1 9003 # 可以参考引用中的链接，了解nc命令 这个时候在nc命令行界面便可以看到打印的init信息
&amp;lt;?xml version=&amp;#34;1.0&amp;#34; encoding=&amp;#34;iso-8859-1&amp;#34;?&amp;gt;\n&amp;lt;init xmlns=&amp;#34;urn:debugger_protocol_v1&amp;#34; xmlns:xdebug=&amp;#34;https://xdebug.org/dbgp/xdebug&amp;#34; fileuri=&amp;#34;file:///Users/jerry/Desktop/work/phptest/index.php&amp;#34; language=&amp;#34;PHP&amp;#34; xdebug:language_version=&amp;#34;7.4.16&amp;#34; protocol_version=&amp;#34;1.0&amp;#34; appid=&amp;#34;29040&amp;#34;&amp;gt;&amp;lt;engine version=&amp;#34;3.0.3&amp;#34;&amp;gt;&amp;lt;![CDATA[Xdebug]]&amp;gt;&amp;lt;/engine&amp;gt;&amp;lt;author&amp;gt;&amp;lt;![CDATA[Derick Rethans]]&amp;gt;&amp;lt;/author&amp;gt;&amp;lt;url&amp;gt;&amp;lt;![CDATA[https://xdebug.org]]&amp;gt;&amp;lt;/url&amp;gt;&amp;lt;copyright&amp;gt;&amp;lt;![CDATA[Copyright (c) 2002-2021 by Derick Rethans]]&amp;gt;&amp;lt;/copyright&amp;gt;&amp;lt;/init&amp;gt; 如果想检测具体的网络协议：
tcpdump -i eth1 -nn -A port 9003 或者wireshark
tcp.port==9003 &amp;amp;&amp;amp; tcp.flags.push == 1 交互测试 import base64 import binascii import re import socket ip_port = (&amp;#39;0.0.0.0&amp;#39;, 9003) sk = socket.socket() sk.bind(ip_port) sk.listen(10) conn, addr = sk.accept() while True: client_data = b&amp;#39;&amp;#39; data_len = 0 data = b&amp;#39;&amp;#39; while True: client_data = conn.recv(1024) if not client_data: break client_data_arr = client_data.split(b&amp;#39;\x00&amp;#39;) # 开始处为内容的长度 if not data_len: data_len = client_data_arr[0] data += client_data_arr[1] else: data += client_data_arr[0] # 最后面是以\x00结尾 if client_data_arr[len(client_data_arr) - 1] == b&amp;#39;&amp;#39;: break print(&amp;#34;[+] Raw Result: %s&amp;#34; % data) g = re.search(rb&amp;#39;&amp;lt;\!\[CDATA\[([a-z0-9=\./\+]+)\]\]&amp;gt;&amp;#39;, data, re.I) if g: data = g.group(1) try: print(&amp;#39;[+] Result: %s&amp;#39; % base64.b64decode(data).decode()) except binascii.Error: print(&amp;#39;[-] May be not string result...&amp;#39;) else: print(&amp;#39;[-] No result...&amp;#39;) data = input(&amp;#39;&amp;gt;&amp;gt; &amp;#39;) # conn.sendall(&amp;#39;eval -i 1 -- %s\x00&amp;#39; % data.encode(&amp;#39;base64&amp;#39;)) conn.sendall(data.encode(&amp;#39;utf-8&amp;#39;) + b&amp;#39;\x00&amp;#39;) 程序简单解释：
监听9003端口（如果其他配置的是其他端口，自行修改程序） '\x00'协议以该字节结尾，循环判断内容是否读取完毕 正则提取xml里的data数据 使用base64解码内容 等待用户输入 发送给服务端命令 循环以上过程 [+] Raw Result: b&amp;#39;&amp;lt;?xml version=&amp;#34;1.0&amp;#34; encoding=&amp;#34;iso-8859-1&amp;#34;?&amp;gt;\n&amp;lt;init xmlns=&amp;#34;urn:debugger_protocol_v1&amp;#34; xmlns:xdebug=&amp;#34;https://xdebug.org/dbgp/xdebug&amp;#34; fileuri=&amp;#34;file:///Users/jerry/Desktop/work/phptest/index.php&amp;#34; language=&amp;#34;PHP&amp;#34; xdebug:language_version=&amp;#34;7.4.16&amp;#34; protocol_version=&amp;#34;1.0&amp;#34; appid=&amp;#34;27099&amp;#34;&amp;gt;&amp;lt;engine version=&amp;#34;3.0.3&amp;#34;&amp;gt;&amp;lt;![CDATA[Xdebug]]&amp;gt;&amp;lt;/engine&amp;gt;&amp;lt;author&amp;gt;&amp;lt;![CDATA[Derick Rethans]]&amp;gt;&amp;lt;/author&amp;gt;&amp;lt;url&amp;gt;&amp;lt;![CDATA[https://xdebug.org]]&amp;gt;&amp;lt;/url&amp;gt;&amp;lt;copyright&amp;gt;&amp;lt;![CDATA[Copyright (c) 2002-2021 by Derick Rethans]]&amp;gt;&amp;lt;/copyright&amp;gt;&amp;lt;/init&amp;gt;&amp;#39; [-] May be not string result... &amp;gt;&amp;gt; breakpoint_set -i 1 -t line -f index.php -n 3 [+] Raw Result: b&amp;#39;&amp;lt;?xml version=&amp;#34;1.0&amp;#34; encoding=&amp;#34;iso-8859-1&amp;#34;?&amp;gt;\n&amp;lt;response xmlns=&amp;#34;urn:debugger_protocol_v1&amp;#34; xmlns:xdebug=&amp;#34;https://xdebug.org/dbgp/xdebug&amp;#34; command=&amp;#34;breakpoint_set&amp;#34; transaction_id=&amp;#34;1&amp;#34; id=&amp;#34;270990001&amp;#34;&amp;gt;&amp;lt;/response&amp;gt;&amp;#39; [-] No result... &amp;gt;&amp;gt; breakpoint_get -i 1 -d 270990001 [+] Raw Result: b&amp;#39;&amp;lt;?xml version=&amp;#34;1.0&amp;#34; encoding=&amp;#34;iso-8859-1&amp;#34;?&amp;gt;\n&amp;lt;response xmlns=&amp;#34;urn:debugger_protocol_v1&amp;#34; xmlns:xdebug=&amp;#34;https://xdebug.org/dbgp/xdebug&amp;#34; command=&amp;#34;breakpoint_get&amp;#34; transaction_id=&amp;#34;1&amp;#34;&amp;gt;&amp;lt;breakpoint type=&amp;#34;line&amp;#34; filename=&amp;#34;file:///Users/jerry/Desktop/work/phptest/index.php&amp;#34; lineno=&amp;#34;3&amp;#34; state=&amp;#34;enabled&amp;#34; hit_count=&amp;#34;0&amp;#34; hit_value=&amp;#34;0&amp;#34; id=&amp;#34;270990001&amp;#34;&amp;gt;&amp;lt;/breakpoint&amp;gt;&amp;lt;/response&amp;gt;&amp;#39; [-] No result... &amp;gt;&amp;gt; breakpoint_list -i 1 [+] Raw Result: b&amp;#39;&amp;lt;?xml version=&amp;#34;1.0&amp;#34; encoding=&amp;#34;iso-8859-1&amp;#34;?&amp;gt;\n&amp;lt;response xmlns=&amp;#34;urn:debugger_protocol_v1&amp;#34; xmlns:xdebug=&amp;#34;https://xdebug.org/dbgp/xdebug&amp;#34; command=&amp;#34;breakpoint_list&amp;#34; transaction_id=&amp;#34;1&amp;#34;&amp;gt;&amp;lt;breakpoint type=&amp;#34;line&amp;#34; filename=&amp;#34;file:///Users/jerry/Desktop/work/phptest/index.php&amp;#34; lineno=&amp;#34;3&amp;#34; state=&amp;#34;enabled&amp;#34; hit_count=&amp;#34;0&amp;#34; hit_value=&amp;#34;0&amp;#34; id=&amp;#34;270990001&amp;#34;&amp;gt;&amp;lt;/breakpoint&amp;gt;&amp;lt;/response&amp;gt;&amp;#39; [-] No result... &amp;gt;&amp;gt; breakpoint_set -i 1 -t line -f index.php -n 11 [+] Raw Result: b&amp;#39;&amp;lt;?xml version=&amp;#34;1.0&amp;#34; encoding=&amp;#34;iso-8859-1&amp;#34;?&amp;gt;\n&amp;lt;response xmlns=&amp;#34;urn:debugger_protocol_v1&amp;#34; xmlns:xdebug=&amp;#34;https://xdebug.org/dbgp/xdebug&amp;#34; command=&amp;#34;breakpoint_set&amp;#34; transaction_id=&amp;#34;1&amp;#34; id=&amp;#34;270990002&amp;#34;&amp;gt;&amp;lt;/response&amp;gt;&amp;#39; [-] No result... &amp;gt;&amp;gt; breakpoint_list -i 1 [+] Raw Result: b&amp;#39;&amp;lt;?xml version=&amp;#34;1.0&amp;#34; encoding=&amp;#34;iso-8859-1&amp;#34;?&amp;gt;\n&amp;lt;response xmlns=&amp;#34;urn:debugger_protocol_v1&amp;#34; xmlns:xdebug=&amp;#34;https://xdebug.org/dbgp/xdebug&amp;#34; command=&amp;#34;breakpoint_list&amp;#34; transaction_id=&amp;#34;1&amp;#34;&amp;gt;&amp;lt;breakpoint type=&amp;#34;line&amp;#34; filename=&amp;#34;file:///Users/jerry/Desktop/work/phptest/index.php&amp;#34; lineno=&amp;#34;3&amp;#34; state=&amp;#34;enabled&amp;#34; hit_count=&amp;#34;0&amp;#34; hit_value=&amp;#34;0&amp;#34; id=&amp;#34;270990001&amp;#34;&amp;gt;&amp;lt;/breakpoint&amp;gt;&amp;lt;breakpoint type=&amp;#34;line&amp;#34; filename=&amp;#34;file:///Users/jerry/Desktop/work/phptest/index.php&amp;#34; lineno=&amp;#34;11&amp;#34; state=&amp;#34;enabled&amp;#34; hit_count=&amp;#34;0&amp;#34; hit_value=&amp;#34;0&amp;#34; id=&amp;#34;270990002&amp;#34;&amp;gt;&amp;lt;/breakpoint&amp;gt;&amp;lt;/response&amp;gt;&amp;#39; [-] No result... &amp;gt;&amp;gt; run -i 1 [+] Raw Result: b&amp;#39;&amp;lt;?xml version=&amp;#34;1.0&amp;#34; encoding=&amp;#34;iso-8859-1&amp;#34;?&amp;gt;\n&amp;lt;response xmlns=&amp;#34;urn:debugger_protocol_v1&amp;#34; xmlns:xdebug=&amp;#34;https://xdebug.org/dbgp/xdebug&amp;#34; command=&amp;#34;run&amp;#34; transaction_id=&amp;#34;1&amp;#34; status=&amp;#34;break&amp;#34; reason=&amp;#34;ok&amp;#34;&amp;gt;&amp;lt;xdebug:message filename=&amp;#34;file:///Users/jerry/Desktop/work/phptest/index.php&amp;#34; lineno=&amp;#34;3&amp;#34;&amp;gt;&amp;lt;/xdebug:message&amp;gt;&amp;lt;/response&amp;gt;&amp;#39; 以上内容为简单演示了程序，分别测试了：设置断点、获取某个断点、列出所有断点、运行。其他演示操作可以参考引用中的文档。
总结 通过程序模拟了dbgp的过程，如果感兴趣还可以通过程序发送system('ls')的命令，感受dbgp协议的流程。关于IDE的配置，主要是在配置里找到xdebug调试端口，在这里对应php配置中的xdebug.client_port。如果修改了，重启一下ide即可。根据是否开启自动调试配置，进行不同方式的触发即可。
更详细的配置，网络上很多，本文主要在于原理分析。
引用 https://xdebug.org/docs/upgrade_guide
https://www.secpulse.com/archives/115172.html
https://wsgzao.github.io/post/nc/ nc命令
https://xdebug.org/docs/dbgp dbgp协议</content></entry><entry><title>aws cloudwatch logs insights分析业务数据</title><url>https://www.d-j.fun/post/technical/2022/0509_aws_cloudwatch_logs_insights_query/</url><categories><category>technical</category></categories><tags><tag>aws</tag></tags><content type="html"> 日志，监控、报表数据来源。那么日志的整个生命周期，产生、收集、存储、清洗、查询 是一个重要的问题。
不过开源的Elastic Stack，很不错的一个平台，提供了契合日志所有生命周期的工具，而且通过es本身技术实现，可以快速的针对上亿日志进行分析。
aws cloudwatch logs是熟悉aws平台的另外一个选择。cloudwatch logs结合了aws各种服务，可以实现无缝对接，而且本身一些服务默认日志存储在cloudwatch logs里。所以熟悉cloudwatch logs 是那些依赖于云服务的公司员工的一个重要技能。
关于CloudWatch Logs 日志来源和收集：服务器的自建日志（通过cloudwatch logs代理收集），aws服务产生的日志 存储： 日志流对应一个来源（比如一个服务器、或服务器上某个服务），日志组整合多个日志流。保留期、访问控制等都是在日志组层。 查询： cloudwatch logs insights提供一整套查询语法，方便使用者的分析。 众所周知，aws的基本所有接口会同时提供三种操作手段：控制台、编程方式、aws命令行。同样针对上面三步的操作。
查询CloudWatch Logs 筛选条件和模式语法 可以通过CloudWatch控制台直接在针对日志组、或着某个具体的日志流进行简单查询，如果只是查看某个RequestId所有日志，还是比较方便的。 &amp;quot;c788ad27-3e3b-4560-ac0c-87caea225078&amp;quot;。将requestid用双引号阔住，输入搜索框中，结果区就会展示该requestid对应的所有日志。
aws logs filter-log-events --log-group-name my-group [--log-stream-names LIST_OF_STREAMS_TO_SEARCH] --filter-pattern &amp;#34;c788ad27-3e3b-4560-ac0c-87caea225078&amp;#34; CloudWatch Logs Insights 如果分析日志，还是得靠CloudWatch Logs Insights强大的查询来做。
fields @timestamp, @message | parse @message &amp;#39;* - * [*] &amp;#34;* * *&amp;#34; * * &amp;#34;-&amp;#34; &amp;#34;*&amp;#34;&amp;#39; as host, identity, dateTimeString, httpVerb, url, protocol, status, bytes, useragent | stats count (*) as all, sum ( status &amp;lt; 299 ) as c_s200, sum ( status &amp;gt; 299 and status &amp;lt; 399 ) as c_s300, sum ( status &amp;gt; 399 and status &amp;lt; 499 ) as c_s400, sum ( status &amp;gt; 499 ) as c_s500 by bin (1m) 查询以管道符分割，支持函数、运算、正则等。
上面的查询分析：
fields：可以使用支持的函数和运算生成新的临时字段，供后面的查询使用。比如 status == 200 as isSuccess。功能就像sql里面的as。示例中列出日志事件中的两个字段，不做变换的情况下可以省略。 parse：从日志字段中提取新的字段。比如通过正则从@message字段中提取出各部分，生成临时字段或者最终结果展示字段。 stats by：等同于mysql中的group by，demo中 count、sum为聚合函数，5m代表将数据以五分钟为单位分割为多个桶，分别聚合不同的指标。 另外还有几个关键字比较简单：
display: 最终结果要展示的字段，类select filter: 筛选指定的条件，简单而又强大。类where limit 翻遍文档，该功能提供的api都是异步的：先提交一个查询，返回一个query_id; 然后自己轮询
func (c *Clogs) Get(group, filter string, start, end int64) (*cloudwatchlogs.GetQueryResultsOutput, error) { req, resp := c.c.StartQueryRequest(&amp;amp;cloudwatchlogs.StartQueryInput{ EndTime: aws.Int64(end), LogGroupName: aws.String(group), QueryString: aws.String(filter), StartTime: aws.Int64(start), }) err := req.Send() if err != nil { return nil, err } for { time.Sleep(30 * time.Second) results, err := c.c.GetQueryResults(&amp;amp;cloudwatchlogs.GetQueryResultsInput{QueryId: resp.QueryId}) if err != nil { return nil, err } if *results.Status != &amp;#34;Scheduled&amp;#34; &amp;amp;&amp;amp; *results.Status != &amp;#34;Running&amp;#34; { if *results.Status != &amp;#34;Complete&amp;#34; { return nil, errors.New(&amp;#34;no complete&amp;#34;) } return results, nil } } } 注意以下几点：
查询超过15分钟，会Timeout，遇到这种情况，分析以下自己的查询语句，适当缩小查询条件 查询状态如果 Scheduled | Running，都需要下次重新获取。 总结 依赖于aws云服务的公司，查看日志的需求蛮常见的。CloudWatch Logs Insights方式虽然灵活强大，但是针对简单查看某个Request的相关日志，反而不如直接筛选方便。
引用 https://docs.aws.amazon.com/zh_cn/AmazonCloudWatch/latest/logs/CWL_QuerySyntax.html 查询语法 https://docs.aws.amazon.com/zh_cn/AmazonCloudWatchLogs/latest/APIReference/Welcome.html API文档 https://docs.aws.amazon.com/sdk-for-go/api/service/cloudwatchlogs/ golang sdk文档</content></entry><entry><title>php set_time_limit函数不起作用原因解析</title><url>https://www.d-j.fun/post/notes/2022/0508_php_set_time_out_not_work/</url><categories><category>notes</category></categories><tags/><content type="html"> 作为phper这么多年，很多时间都花在业务、框架上，反而一些细节不经意的就抽自己一巴掌。本文解析set_time_limit配置，以及相关细节。也记录一下此次掉坑经历，给自己以后指一个方向。
起因 服务器某个接口相应时间长达100s，而且报nginx 502错误。检查发现php max_execution_time使用的默认值30s。错误分析：502 gateway错误，可以定位到是php脚本处理太慢造成的。但是让人怀疑的是为什么30s脚本不停止。
解析 官方解释 The set_time_limit() function and the configuration directive max_execution_time only affect the execution time of the script itself. Any time spent on activity that happens outside the execution of the script such as system calls using system(), stream operations, database queries, etc. is not included when determining the maximum time that the script has been running. This is not true on Windows where the measured time is real. 翻阅官方文档得到答案：该配置项仅仅代表脚本本身的执行时间，而不包括系统调用、流操作、数据库查询所占用的时间（即不计算 sleep,file_get_contents,shell_exec,mysql_query等花费的时间）。
起因中提到的问题就找到原因了：脚本本身查询mysql耗费了太久时间，导致达到nginx proxy_read_timeout 100时间限制。nginx在发现proxy即fpm没有在规定时间内返回结果，就直接返回调用方502错误。
自我验证 &amp;lt;?php error_reporting(0); ini_set(&amp;#39;display_errors&amp;#39;, &amp;#39;off&amp;#39;); // set_error_handler 不能捕获致命错误 register_shutdown_function(function () { echo sprintf(&amp;#34;脚本结束时间%f\n&amp;#34;, microtime(true)); }); echo sprintf(&amp;#34;脚本开始时间%f\n&amp;#34;, microtime(true)); for($i=0;$i&amp;lt;100000000;$i++){ sha1(time()); } echo sprintf(&amp;#34;第一次循环结束时间%f\n&amp;#34;, microtime(true)); set_time_limit(10); $i = 0; while ($i &amp;lt;= 10) { echo &amp;#34;i=$i &amp;#34;; sleep(2); $i++; } $end = microtime(true); var_dump(getrusage()); echo sprintf(&amp;#34;\n第二次循环结束时间%f\n&amp;#34;, microtime(true)); while (true) { sha1(time()); } echo sprintf(&amp;#34;\n第三次循环结束时间%f\n&amp;#34;, microtime(true)); // output //脚本开始时间1651993104.293944 //第一次循环结束时间1651993191.067107 //i=0 i=1 i=2 i=3 i=4 i=5 i=6 i=7 i=8 i=9 i=10 //第二次循环结束时间1651993213.096094 //脚本结束时间1651993223.126825 第一次循环占用时间 86.77s (时间1) 第二次循环占用时间 22.02s (时间2) 第三次循环占用时间 10.03s (时间3) 脚本解析：
时间1是在set_time_limit执行前的脚本执行时间，说明set_time_limit执行的时候，时间计数器从0重新计数 时间2是因为sleep了11次，每次2s。说明循环本身只是占用了0.02s 时间3是set_time_limit开始到脚本Fatal Error退出执行之间的时间。虽然第二次循环占用了22s，但是sleep不被计数。 设置真实执行时间 &amp;lt;?php echo sprintf(&amp;#34;脚本开始时间%f\n&amp;#34;, microtime(true)); pcntl_async_signals(1); pcntl_signal(SIGALRM, function () { echo sprintf(&amp;#34;脚本结束时间%f\n&amp;#34;, microtime(true)); exit(&amp;#39;Stop it!&amp;#39;); }); pcntl_alarm(3); $i = 0; while (true) { $i++; sha1(time()); echo &amp;#34;i = {$i}\n&amp;#34;; } echo sprintf(&amp;#34;脚本结束时间2%f\n&amp;#34;, microtime(true)); // output 脚本开始时间1651994735.305631 i=33万次... 脚本结束时间1651994738.307060 Stop it! 或者使用fork调用，父进程监控子进程运行。
$pid=pcntl_fork(); if ($pid) { while (true) { shell_exec(&amp;#39;sleep 10&amp;amp;&amp;#39;); } } else { sleep(5); posix_kill(posix_getppid(),SIGKILL); } 如果是CLI脚本，有更多解决方案：
timeout 5 /usr/bin/php -q /path/to/script 总结 不建议使用set_time_limit(0), 因为脚本会一直执行下去，影响服务器负载 脚本默认max_execution_time为0，即会一直执行下去，建议设置该配置，因为脚本会永久占用进程。 set_time_limit 会重置时间计数。比如在脚本已经运行20s的时候调用set_time_limit(10),那么脚本执行时间为30s。如果运行一个耗时任务的时候，一般放在脚本第一行。 sleep,file_get_contents,shell_exec,mysql_query这些函数包含在设置的时限内，所以这些函数本身执行时间应该自己考虑在内，不要让时间浪费在没必要的事情上。 set_time_limit 不能使用在安全模式下。set_time_limit() [function.set-time-limit]: Cannot set time limit in safe mode</content></entry><entry><title>上海在《这个春天》郝景芳</title><url>https://www.d-j.fun/post/thoughts/2022/0508_this_spring/</url><categories><category>thoughts</category></categories><tags/><content type="html"> 被疫情阻隔的春天、岁月和一去不复返的过去、我们
这个春天
我们看见悲伤的形状
悲伤被记忆炖煮
在空寂的盘中释放清香
这个春天
花朵盛放
寂寥的街景无人观赏
花的命运与花椰菜一样
因一道禁令，抵达不了人的欲望
这个春天
我们无比怀念远方
我们微笑着回忆那年在路上
星空下点篝火，闻林间青草香
我们一直微笑着遐想
在饥饿中遐想
这个春天
我们见到了死亡的模样
哭声在窗外回荡
刺在心上，堵住倾诉的愿望
那粒灰尘什么时候落到自己头上
物伤其类，目睹神伤
生命随河水流淌
岂知愤怒会不会被时间埋葬
这个春天
灵魂要压抑对自由的渴望
良心要回避对正义的主张
智能机器在寻访
人类沟通在躲藏
善良的人呈现出暴力倾向
软弱的人承担抗争的坚强
黑市肆意疯长
人们拼死奔忙
只想寻找活下去的方向
这个春天
有人撒谎
有人刺破他人撒谎
有人禁止刺破他人撒谎
有人见证禁止刺破他人撒谎
这个春天
我们识得人生的方向
只愿在饥饿时留一点善良
在黑暗时留一点光
那些在疲惫困顿中保持慈悲的人们啊
我愿在远方的路上为你们歌唱
愿大地记住你们的顽强
愿风记住你们的悲伤</content></entry><entry><title>使用jenkins pipeline部署Gcloud Function</title><url>https://www.d-j.fun/post/notes/2022/0506_jenkins_pipeline_deploy_gcloud_function/</url><categories><category>notes</category></categories><tags/><content type="html"> Google Cloud Function、aws lambda都是类似的无服务器服务，一种轻量级计算解决方案，通过相应平台封装的对应事件，返回响应。无需管理服务器、并且可以根据负载量自动扩缩容，这些特点特别方便我们一些高负载的需求。
但是代码部署方式上，和在服务器上自主搭建服务是有差别的：gcf接受Cloud Source Repositories（google的代码库平台）、zip文件；aws lambda 接受编译好的二进制文件的压缩包。不过aws 和 gcloud 都提供了强大的命令行工具，配合jenkins部署是一个不错的选择。由于gcloud、aws机制类似，本文以gcloud为例。
Google Cloud Function 部署 代码载体选择 google cloud function支持四种：
内嵌编辑器： 只适合一些简单的测试 zip文件上传：将zip打包比较适合jenkins，jenkins对shell脚本支持的比较好，但是gcloud命令没有看到接受文件上传的选项。 cloud storage上的zip：google cloud storage对应aws的s3，将代码压缩好上传到这里还可以备份，是比较好的选择 Cloud Source Repositories：考虑到一般公司都选择自建的gitlab仓库，需要做一层同步。 通过比较几种代码载体，第三种是适合博主的方式，具体选择哪种，视你的具体场景选择。
gcloud sdk安装 安装教程见 Google CLi安装 。一般服务器linux操作系统错不了，简单整理官方文档步骤如下:
1 2 3 4 5 6 7 8 9 # 下载google cloud sdk压缩包 curl -O https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/google-cloud-sdk-377.0.0-linux-x86_64.tar.gz # 解压 tar -xf google-cloud-sdk-377.0.0-linux-x86.tar.gz # 执行安装sh脚本 ./google-cloud-sdk/install.sh # 由于要把代码上传到cloud storage，用到alpha组件 gcloud components install beta #接下来不要执行gcloud init，jenkins部署提供了secret file认证文件的管理，为了安全。 以上操作是在jenkins同台机器上执行。
gcloud sdk认证 首先判断是否已认证：gcloud auth list。该命令会列出已认证的账号，以及活跃账号。 google iam提供了几种认证方式，因为要在jenkins pipeline里使用，最好选择gcloud auth activate-service-account。当然也可以选择直接在jenkins机器jenkins用户下直接执行gcloud init。 即使gcloud auth activate-service-account该种方式认证，gcloud也会将私钥的副本存储在$HOME/.config/gcloud目录下面。如果要可靠的存储gcloud身份验证信息，在运行完pipeline之后，清楚一下本地缓存的密钥文件rm -rf $HOME/.config/gcloud。
另外建议创建权限足够但不多余的专门用户，服务于jenkins。权限请选择：Cloud Functions Admin、Storage Admin。 jenkins pipeline部署 pipeline介绍 jenkins pipeline 流水线是用户定义的一个cd模型，通过代码定义项目的整个构建过程，支持各种组件，灵活而强大。类似的有github runner、bitbucket pipeline以及比较通用的github actions。
pipeline { agent any stages { stage(&amp;#39;Build&amp;#39;) { steps { sh &amp;#39;make&amp;#39; } } stage(&amp;#39;Test&amp;#39;){ steps { sh &amp;#39;make check&amp;#39; junit &amp;#39;reports/**/*.xml&amp;#39; } } stage(&amp;#39;Deploy&amp;#39;) { steps { sh &amp;#39;make publish&amp;#39; } } } } 简单的语法定义将构建过程分成步骤stage，同时支持环境变量定义，同时jenkins还提供了pipeline-syntax片段生成器。详细参考Jenkins Pipeline文档。
添加密钥文件 在jenkins系统管理-全局凭据中，添加密钥，选择 secret file。具体可参考 []
jenkins pipeline部署Gcloud Function pipeline示例 pipeline { agent any stages { stage(&amp;#39;克隆&amp;#39;) { when { branch &amp;#39;main&amp;#39; } steps { dir(path: &amp;#34;./&amp;#34;) { checkout([$class: &amp;#39;GitSCM&amp;#39;, branches: [[name: &amp;#39;*/master&amp;#39;]], doGenerateSubmoduleConfigurations: false, userRemoteConfigs: [[credentialsId: &amp;#34;仓库git凭证&amp;#34;, url: &amp;#39;仓库地址&amp;#39;]]]) } } } stage(&amp;#39;上线&amp;#39;) { when { branch &amp;#39;main&amp;#39; } steps { withCredentials([file(credentialsId: &amp;#39;gcloud secret file id&amp;#39;, variable: &amp;#39;GCP_FILE&amp;#39;)]) { sh &amp;#39;git archive -o /tmp/code.zip HEAD&amp;#39; sh &amp;#34;gcloud auth activate-service-account 账户名称 --key-file=&amp;#39;${GCP_FILE}&amp;#39;&amp;#34; sh &amp;#39;gcloud alpha storage cp /tmp/code.zip gs地址&amp;#39; sh &amp;#39;gcloud functions deploy 函数名 --source gs地址&amp;#39; } } } stage(&amp;#39;测试&amp;#39;) { steps { sh &amp;#39;curl -vvv 链接&amp;#39; } } } } 触发构建，可以根据自己需要设置，例子中是main分支有更新。还可以选择手工点击构建。
可能遇到的问题 gcloud sdk not found类似问题：安装gcloud cli必须安装在jenkins所在机器 User does not have the 'iam.serviceAccounts.actAs' permission on *@appspot.gserviceaccount.com required to create the function. You can fix this by running gcloud iam service-accounts add-iam-policy-binding *@appspot.gserviceaccount.com --member=user: --role=roles/iam.serviceAccountUser&amp;quot;,详见 官方解释 ：是因为创建用户和部署用户不同，解决办法为 gcloud iam service-accounts add-iam-policy-binding 创建function用户 --member=serviceAccount:serviceaccount用户 --role=roles/iam.serviceAccountUser 总结 jenkins pipeline是一个简单而又强大的cd工具，可以从平时复杂冗余的工作中解放出来。由于对shell及jenkins本身强大的插件机制，可扩展的地方很多，本文只是拿一个具体的任务举例。目前比较可惜的是pipeline类似的功能都是在具体的平台上，没有一个更宽泛的概念，期待。。。
引用 https://cloud.google.com/sdk/gcloud/reference/functions/deploy gcloud sdk function文档 https://tech.ray247k.com/blog/202204-jenkins-cicd-3-push-docker-image-to-gcr/ Jenkins 打包 Docker image 並推送到 GCR</content></entry><entry><title>curl 关于服务时间性能的指标探究及应用</title><url>https://www.d-j.fun/post/notes/2022/0501_benchmark_server_latency_with_curl/</url><categories><category>notes</category></categories><tags><tag>Linux命令</tag></tags><content type="html"> 作为一个服务端开发，强烈意愿需要一个性能检测工具，而时间是性能一个重要的指标。而curl 请求输出可以根据自己需要设置（time_namelookup、time_connect、time_appconnect、time_pretransfer、time_starttransfer、time_total）时间相关的输出。但是搜索了google、baidu不止一天两天，得到的答案都是man curl，而没有一个更精准、容易理解的答案。最终无意中搜索到&amp;lt;引用&amp;gt;的文章，茅塞顿开，建议读到本篇文章的都耐心读完。
预备知识 curl标准化输出 在一个请求结束时，curl会根据-w，&amp;ndash;write-out 选项打印出来定制化信息到标准输出。format 包含有占位符的字符串，curl会替换占位符为预定义的变量的值。
比如 HTTP状态码：%{http_code} 会被替换为 HTTP状态吗: 200
时间指标官方解释 时间指标的单位都是秒，计时开始都是curl请求开始时间，也就是dns开始查询那一刻的时间。
time_namelookup：开始到dns查询完成花费时间 time_connect：开始到tcp三次握手完成花费时间 time_appconnect：开始到ssl握手时间完成花费时间 time_pretransfer: 开始到文件传输即将开始花费时间 time_starttransfer: 从开始到第一个字节即将被传输，包含time_pretransfer和服务器计算结果所需时间 time_total 整个操作所有花费时间 理解重点 一个容易被忽略的前提，curl作为一个客户端工具，计算所有时间的前提都代表着网路传输的客户端。理解每个时间，必须先提醒自己这个前提。
探究过程 请求例子 /usr/local/opt/curl/bin/curl -L -w &amp;#39;@curl.txt&amp;#39; &amp;#39;localhost:8080/a.php&amp;#39; time_namelookup: 0.002783 time_connect: 0.002928 time_appconnect: 0.000000 time_redirect: 2.104259 time_pretransfer: 0.002981 time_starttransfer: 6.177934 ---------- time_total: 6.178134 例子详情：
// a.php &amp;lt;?php sleep(2); header(&amp;#34;location:./b.php&amp;#34;); // b.php &amp;lt;?php sleep(4); time_namelookup指标 这个很好理解，dns查询时间，即curl访问某个域名的时候，需要先从dns服务器以下该域名对应的ip。这个过程一般会很快，是因为服务器在首次获取到ip的时候，会缓存结果一段时间。我在本地测试/etc/hosts解析的域名，时间保持在2～3ms左右。
time_connect指标 网络建立socket需要经历三次握手，该指标的值就是开始到建立socket成功的时间。time_connect-time_namelookup即curl和服务器建立socket所花费的时间，对应上面的例子花费了0.145ms。测试了baidu，该指标为21ms。
time_appconnect指标 真正的传输建立完socket便可以，比如http便是直接在tcp协议上传输内容。但是https为了网络内容的安全会在tcp上再次进行ssl层的建立，该阶段百度测试120ms。所以该指标也是一个很重要的查询整体性能的因子。上面的例子由于是http，故该指标值为0。
time_redirect指标 从上例子中可以看出，该指标是截止点在于跳转到b之前的瞬间。由于a文件在跳转之前先等待了2s，所以curl接收到301请求之后，由于-L（follow redirects），会立马请求b.php，但是b.php的返回是4s之后，所以该时间点，应该是接收到301之后，再次请求服务端的瞬间。
这里就牵扯到一个问题：如果header location 是其他网站，是不是要重新计算namelookup的时间，以及建立链接的时间。
// a.php &amp;lt;?php sleep(2); header(&amp;#34;location:https://www.baidu.com&amp;#34;); time_namelookup: 0.090075 time_connect: 0.158963 time_appconnect: 0.214078 time_redirect: 2.412030 time_pretransfer: 0.308881 time_starttransfer: 2.570669 ---------- time_total: 2.577325 可以看到time_namelookup、 time_connect、time_appconnect都有较大的变化，如果单独请求百度得到的三个值也会小于上面三个值。所以三个指标如果包含跳转，应该是跳转前后两次的对应加和。
该指标指最后一个请求开始的瞬间，只考虑整个链条的最后一次请求。
最困惑的time_pretransfer指标 从上面的例子可以发现该指标会比time_appconnect（https）或time_connect（http）多几十微秒，如果是跳转会多点，但是相对于网络来回来说，这个时间太小。所以有的文档说，该指标只是为了和connect概念上做区分。但是从细微的时间上的差别来看，足够cpu做很多事情，所以它表示的应该是，在connect之后处理了一些协议相关的初始化操作，然后将数据放到网卡之前的瞬间。
通俗的说，该指标结束点在于开始往服务器发送真正的http、https请求的瞬间。
time_starttransfer和time_total指标 这两个指标应该是比较好理解的。time_starttransfer 对应TTFB，即curl收到服务端传来的第一个字节的瞬间。time_total整个curl操作周期的总时间。
指标应用 dns解析快慢，直接使用time_namelookup 链接创建时间，根据http\https, 使用time_connect\time_appconnect - 直接使用time_namelookup 服务端处理时间，time_starttransfer - time_pretransfer 内容下载时间，time_total - time_starttransfer 总结 curl的几个时间指标真实反应一次请求的各个阶段，对于服务时间性能方面的测试是一个很大的助力。关于本文，主要针对无法理解curl man的解释，做了各个方面的探究。这些探究都是针对实际操作而得出的结论，可能无法真实反应curl本身源代码方面的设计。
不过针对平时的应用，参考&amp;lt;指标应用&amp;gt;小节，完全可以满足平时所用。如果针对某个具体的指标有疑问，可以深入到具体的指标实验中做对比。
引用 https://speedtestdemon.com/a-guide-to-curls-performance-metrics-how-to-analyze-a-speed-test-result/</content></entry><entry><title>Github Pages最佳实践, 基于Github Actions</title><url>https://www.d-j.fun/post/notes/2022/0501_github_pages_best_practices/</url><categories><category>notes</category></categories><tags><tag>Github</tag></tags><content type="html"> 搭建本博客使用到的技术是hugo结合github page，详见hugo自建博客。博客中可以看到详细过程：将hugo写作环境推到github main分支，通过github action构建到gh-pages分支。这是目前见到的大部分方案，也有通过在其他仓库（gitee）将最终结果推送到github上的。
最佳实践 个人觉得最佳的方案，应该隐藏写作环境，即hugo最终生成静态文件依赖的模板、md文件等。否则复制一个网站的成本就太小了，有心的人只需要下载你的写作环境，本地重新build以下，即可完全仿造你的整个流程。当然编译后的静态文件总归要暴露出去，但是后续更新、批量修改都增加了仿冒的成本。
针对本博客的实现方案，最佳实践应该为 main分支隐藏，gh-pages分支继续保留。
实现方案 构想过集中方案，可以根据自己情况实施：
自己有服务器的，可使用的方案很多，写作环境保留在自己服务器即可，静态文件可以存放服务器，或者推送到github pages 建立两个Github仓库（注意创建顺序），写作环境保存在私有仓库，推送到另外一个公开仓库里面 类似2，不过写作环境放在自己搭建的仓库更安全，或者国内gitee、国外aws gitcommit 我选择第二种方案测试了一下
创建私有仓库 由于Github 二级域名默认在账号级别创建的第一个仓库为根目录，具体实施应该为：
静态文件推送到账号下第一个创建的仓库 写作环境推送到账号下除第一个创建仓库以外的仓库 我的做法：将第一个仓库的内容删除，然后本地git remote add origin 第二个仓库地址
构建实现 peaceiris/actions-gh-pages 该该组件是编译好的静态文件推送到Github Pages，集成于Github Actions。详细见 hugo自建博客 Github Actions yaml文件。
- name: Deploy # 部署 uses: peaceiris/actions-gh-pages@v3 with: personal_token: ${{ secrets.PERSONAL_TOKEN }} publish_dir: ./public cname: www.d-j.fun external_repository: micywin/hugo-dj 注意yaml和原文章的区别，由于原文章是将构建好的静态文件推送到当前仓库的gh-pages分支，直接使用Github Actions初始化的Github_TOKEN即可，但是如果跨仓库，甚至于不通的仓库提供商，那么这里就要设置为对应的token。由于博主使用的是github的另外一个仓库，使用personal_token即满足需求。
按照上图设置好secrect之后，注意external_repository，这里是设置相对于当前仓库的另外一个仓库。
这样设置好之后，重新推送一下写作环境的代码到第二个仓库，那么没有问题情况下，第一个仓库的gp-pages分支会出现最新一版的静态文件。
拓展内容 关于peaceiris/actions-gh-pages该组件还有更多的配置选项，可以根据自己需要去配置，个人觉得比较有用的：
设置full_commit_message, 让每次更新可以看到提交的相关信息 如果不想部署的时候影响到在线阅读体验，可以定时在半夜部署 等，可以根据自己情况去配置。博主的Deploy的完整配置：
- name: Deploy uses: peaceiris/actions-gh-pages@v3 with: personal_token: ${{ secrets.PERSONAL_TOKEN }} publish_dir: ./public cname: www.d-j.fun external_repository: micywin/hugo-dj full_commit_message: ${{ github.event.head_commit.message }} 总结 为了写作体验，和后续可能的问题，应该提前做好各种规划。比如本文提到的写作环境隐藏、自动给图片添加水印、还有目录的规划、文章分类的划分等。</content></entry><entry><title>使用hugo在github上搭建独立博客</title><url>https://www.d-j.fun/post/technical/2022/0426_hugo_hosted_on_github/</url><categories><category>technical</category></categories><tags><tag>Github</tag><tag>Hugo</tag></tags><content type="html"> hugo golang编写的静态化网站构建工具，速度、灵活是两个标榜的特点。选择该工具的原因：有自己喜欢的模版、方便的自定义、快速的调试，最关键是可以git版本化管理内容，不用依托于mysql，这是最关键的原因。
github pages更是提供了免费的托管，同时github actions提供自动化编译，搭配起来是完美。
纵览搭建过程 Hugo 在本地初始化git仓库，在该仓库中搭建hugo环境，主要是选择自己喜欢的模板 Hugo 主题 。接下来比较耗费时间的是，根据自己需要修改模板：比如title、description，还有一些样式，很多没有定义在配置里，就需要自己在layouts中找到对应的文件进行修改，不过hugo的文件结构比较清晰，有过网站开发经验的很容易找到规律。
Github 本地准备完毕后便可以将整个hugo环境推到github，然后利用github actions自动化构建，便完成基础搭建。 hugo官方提供的有github actions yml文件，可以根据自己需要改动。
一般的流程是：
拉取代码 hugo编译 代码推送到gh-pages分支 最终在github仓库的设置里，将pages分支设置为gh-pages，便可以通过【github username】.github.io网址，看到自己的博客雏形了。
详细搭建过程 本小节除了给出一些必要的安装步骤，还会推荐一些工具搭配，除了有利于搭建过程、对以后博客坚持写作也有帮助。同时一些安装过程中踩到的坑，也会在具体步骤中标注。
Hugo环境 官网给出了无比详细的、各个平台的安装步骤 Hugo 安装 , 直接选择自己的操作系统，一步步安装即可。由于博主开发机是mac pro，直接一步到位 brew install hugo
打开 terminal (推荐安装iTerm2), 输入hugo version, 如果输出hugo版本，代表安装成功，并正确配置了执行路径。如果提示找不到命令，原因可能是 hugo执行程序不在环境变量PATH中，将程序放到PATH路径中，一般是bin、/user/bin等，或者执行export PATH=&amp;quot;$PATH:hugo安装路径&amp;quot;，再次执行hugo version。一般的问题都出在这里，如果不符合自己的情况，可以再仔细阅读官方安装文档，或者通过评论联系博主。
新建网站：hugo new site [eight]。该命令会在执行命令的当前路径，新建eight文件夹，目录中比较重要的有themes目录, config.toml。执行完命令，用一款编辑器打开目录，熟悉一下目录结构，毕竟以后写博客都要面对这些目录。推荐安装sublime，博主开发环境都选择idea，随意选择了goland。无论选择什么编辑器，方便的文件管理、目录结构、markdown编辑预览功能是必须具备的。工具一定要选择一个适合自己，功能方便的，涉及到自己的写作体验。
Hugo主题 hugo主题选择，个人喜好问题，不过不建议花太多时间在这上面，毕竟搭建博客是为了记录、分享内容（虽然博主纠结了好久，最终选择了next）。
hugo主题安装三种选择，不同的选择，各有优劣，视自己具体情况而定：
git submodules: git submodule add 主题仓库地址 themes/主题名称，该方法适合于以后完全不会自己修改模板的人，好处是如果模板主题作者修改了模板，还可以随时更新到最新。但是如果想自定制一些功能，就得去原作者仓库提交pr。 将文件复制到themes目录：在其他目录下载完themes文件，将文件夹复制到themes目录下，随着你的git仓库一块提交，相当于在你的仓库里增加文件内容。这样你可以随意修改模板、样式，但是相对于根目录来说，themes有很多部分是重合的，如果是极度洁癖慎用。 将模板对应的文件夹，覆盖根目录对应文件：该方法具有第二种方法的随意修改的有点，而且文件也不会出现冗余，博主选择该方法。 执行hugo server，在命令提示中可以看到 localhost:1313 (如果你在另外一个地方执行过了，可能是其他端口)，将该地址复制到浏览器地址栏，点击访问，便可以实时预览自己的博客。如果你修改了博客中的模板、样式、文章等，保存后便可以在这个地址看到。
这个时候，便可以修改logo、博客名称、博客描述，最好加一篇文章方便调试。
Github Pages部署 上面步骤好了之后，便可以部署github pages了。注意一点：一个github账号，只有一个github.io的域名（【github username】.github.io），如果不是规划该账号下，多个项目对应一个域名，那么建议新建一个github账号。
假设你的账号用户名 eight（小八），那么可以新建一个public仓库eight.github.io (这样命名，github会默认开启pages功能，不过不强求)，copy仓库地址。然后在本地仓库执行git remote add origin 仓库地址，然后将代码推送到GitHub仓库。有一个点，github主分支默认是main，本地一般都是master。具体用哪个自己偏好决定。
在项目根目录添加.github/workflows/gh-pages.yml文件：
name: github pages # 任务名称 on: push: branches: - main # 触发分支 jobs: deploy: runs-on: ubuntu-20.04 # 任务执行环境 steps: # 每个步骤：name 步骤名称，uses 使用组件，with 参数 - name: Check out repository code # 下载代码 uses: actions/checkout@v2 with: submodules: true - name: Setup Hugo # 下载hugo uses: peaceiris/actions-hugo@v2 with: hugo-version: &amp;#39;latest&amp;#39; ## extended: true - name: ls # 最好加这一步，要不然报错都不知道为啥，查看工作空间文件 run: ls ${{ github.workspace }}/themes/hugo-theme-next2/ - name: Build # 构建 run: hugo --minify - name: Deploy # 部署 uses: peaceiris/actions-gh-pages@v3 with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./public cname: www.d-j.fun 推送到github之后，会自动按照这个yaml文件的脚本执行，注意分支名字，如果不对，该workflows不会执行。如果没有执行成功，点击github 仓库的actions按钮，然后找到具体的action，点开可以查看详细log记录。成功后仓库会多一个gh-pages分支。
特别需要注意：cname文件，如果要使用自定义的域名，cname参数会使 peaceiris/actions-gh-pages@v3 添加一个cname，cname文件如果缺失，会造成github仓库设置里面的自定义域名丢失lost。。。
后续操作 这个时候访问eight.github.io，便可以看到自己的博客。 不同的模板需要的配置不一样，还需要稍微的微调。如果想自定义域名，点击github仓库的设置，找到pages，在custom domain填入自己的域名。有个先提条件，需要将域名cname到eight.github.io。
Hugo自建博客总结 整个过程步骤不多，但是小细节很多。比较耗费时间的是下载主题总归会有需要修改的地方，这个比较耗费时间。还有应该着重在内容上，不要太纠结于一些界面上的瑕疵。一定要选择自己满意的编辑器，特别是修改layouts布局文件的时候，以及发布文章。否则会被一些格式上面的问题困扰。
感谢 本博客搭建参考了 兰陵子 的搭建过程，也参考了 凡梦星尘 的优化笔记 。</content></entry></search>