2024年2月

日常运维工作中有许多的任务要执行,例如项目发布/数据备份/定时巡检/证书更新/漏洞修复等等,大部分的任务都会有多个步骤共同完成,例如一个发布任务会有拉代码、编译、分发、通知等等步骤,而不同的任务可能还包含相同或相似的步骤,例如对于相同语言相同架构的不同项目来说,他们的发布任务步骤可能是相同的,仅仅是几个参数不同而已,为此我们上线了
任务系统
,任务系统可以像乐高搭积木一样组合不同的步骤(也就是子任务)为一个模板,然后基于模板配合参数来创建任务并执行,灵活且高效

其中子任务的类型越丰富则上层模板的组合方式就会越多样,从而最终的任务覆盖场景就能越全面,目前任务系统子任务的类型已经支持了命令、脚本、作业、Jenkins、审批、通知、API、告警策略和事件规则,之前有篇文章
任务系统之Jenkins子任务
介绍过Jenkins类型子任务的实现,那这篇文章来介绍下另一个很重要的子任务类型:API

API的使用场景非常之广,在现代的Web系统中绝大部分的操作都可以由API来完成,任务系统对API类型子任务的支持不仅使得任务系统的覆盖场景更加的全面,同时也使得API的调用更加的方便和安全。在未支持API类型子任务之前,任务系统都是通过脚本类型子任务由用户自己编写脚本来实现API调用的,这种方式虽然可用但存在问题,其一是因为写脚本对用户有一定的能力要求,同时增加了复杂度,其二也更为关键的是不安全,API往往需要验证,验证内容写在脚本中,脚本明文存储在数据库中,增加了安全风险,集成API类型子任务可以完美解决这两个问题

API类型子任务的使用方式很简单,只需要三步,第一步新建API,第二步新建API类型的子任务,这里选择第一步创建的API,第三步在任务模板中添加第二步创建的子任务,然后就可以新建任务执行了。其中第二第三步是任务系统的通用流程,这里重点说下第一步关于API的创建,API与Jenkins一样是作为一个插件存在于系统中的,由独立的API管理页面

点击右上角添加按钮之后就可以添加新的API了,创建表单主要包含名称、请求、参数和成功条件四块内容

名称就是个输入框,标识这个API的名称,清晰易理解就好

请求和参数用来配置这个API的主要属性,包含了Method、URL、Parameters、Body、Headers、Cookies和Authentication认证方式配置,页面参考了Postman的设计。其中认证方式的话支持Basic、BearerToken和APIKey,后续也可以根据需要新增更多的认证方式,甚至直接接入多云的认证,实现云上API的便捷使用,以弥补多云系统的不足

前后端通过JSON格式实现数据交互,前端根据输入的请求和参数组装成JSON格式提交给后端,后端则根据认证方式加密对应的密码或Token字段并保存到数据库,当任务请求发起时从数据库获取JSON数据并解密,解析解密后的JSON数据调用Requests模块请求并获取结果

最后一块是判断API是否请求成功的规则集,默认情况下会判断API的返回状态码,如果状态码为2xx或者3xx都认为是成功的,当然用户也可以根据状态码或是响应体自定义成功的规则,例如某些情况下你可能仅需要API返回200表示成功,或者是响应体里的state字段为1算成功,都可以通过自定义灵活的配置。判断API是否请求成功很重要,在一个任务流中,某个步骤的成功与失败直接影响后续步骤的执行,API也不例外,当API任务执行失败时则终止任务流程的执行

前端组装好的JSON格式参考如下

{
  "name": "获取Github仓库blog的branches",
  "api": {
    "method": "GET",
    "url": "https://api.github.com/repos/ops-coffee/blog/branches",
    "parameters": [],
    "body": "",
    "headers": [{"name": "Content-Type", "note": "", "type": "string", "value": "application/json"}],
    "cookies": [],
    "authorization": {
      "type": "Bearer",
      "bearer_token": "ghp_3dtK****************IIcN",
    }
  },
  "success": [
    {"key": "status_code", "type": "状态码", "value": ["200"], "operator": "匹配"}, 
    {"key": "name", "type": "响应体", "value": "main", "operator": "等于"}
  ]
}

后端加解密的方法可以参考文章
Model字段加密的优雅实现
,Request请求则比较简单

# 不同的认证类型使用不同的请求方式
if authorization.get("type", "").lower() == "basic":
    auth = (authorization.get("basic_username", ""), authorization.get("basic_password", ""))
    response = requests.request(method, url, params=parameters, headers=headers, cookies=cookies, auth=auth,
                                data=body)

else:
    if authorization.get("type", "").lower() == "bearer":
        headers["Authorization"] = "Bearer " + authorization.get("bearer_token", "")

    elif authorization.get("type", "").lower() == "apikey":
        headers[authorization.get("api_key_key")] = authorization.get("api_key_value", "")

    response = requests.request(method, url, params=parameters, headers=headers, cookies=cookies, data=body)

# 解析Response结果判断是否成功
......

API创建完成后就可以添加子任务,添加了子任务就可以将子任务添加到模板,最后创建任务并执行了,执行结果实时输出

至此API类型子任务介绍完成,API类型子任务快速打通了任务系统与三方的链接,为任务系统增加了无限可能,期待它在业务中的表现

Lua调试的时候会用到debug.getinfo()函数,
what
的值文档给了解释:

  • "Lua" : Lua function
  • "C" : C function
  • "main" : main part of a chunk (通过load函数等执行的语句)

关于
namewhat
的值到底表示什么,
官方文档
只是简单列举(不全)。

列举一些常见情况

local getinfo = debug.getinfo
local format = string.format
local hook = function(event, line)
  local t = getinfo(2, "nS")
  local msg = format("[%s:%s] %s (%s:%s)", t.what, t.namewhat, t.name, t.source, t.linedefined)
  print(msg)
end
debug.sethook(hook, "c")

local M = {}

print(123)
-- [C:global] print (=[C]:-1)

local _print = print
_print(123)
-- [C:local] _print (=[C]:-1)

function M.Func1(a)
end

function M:Func2(a)
end

M.Func1()
-- [Lua:field] Func1 (@.\test.lua:19)
M.Func2()
-- [Lua:field] Func2 (@.\test.lua:22)
M:Func2()
-- [Lua:method] Func2 (@.\test.lua:22)

local list = {1, 2, 3}
for i in pairs(list) do
  local a = 0
end
--[[
  [C:global] pairs (=[C]:-1)
  [C:for iterator] for iterator (=[C]:-1)
  [C:for iterator] for iterator (=[C]:-1)
  [C:for iterator] for iterator (=[C]:-1)
]]

local t = { a = 1 }
setmetatable(t, {
  __index = function(t, k)
  end
})
local a = t.a
local b = t.b
-- [Lua:metamethod] index (@.\test.lua:46)

function GlobalFunc1()
end

function GlobalFunc2()
  return GlobalFunc1()
end

GlobalFunc2()
--[[
  [Lua:global] GlobalFunc2 (@.\test.lua:56)
  [Lua:] nil (@.\test.lua:53)
--]]

function M.Func3()
  local a = 1
  local closure_func = function()
    a = a + 1
  end

  closure_func()
  return a
end

M.Func3()
--[[
  [Lua:field] Func3 (@.\test.lua:66)
  [Lua:local] closure_func (@.\test.lua:68)
]]

function M.Func4(f)
  f()
end

M.Func4(function() end)
--[[
  [Lua:field] Func4 (@.\test.lua:82)
  [Lua:local] func (@.\test.lua:86)
]]

local FileFunc1
local FileFunc2

FileFunc1 = function()
end

FileFunc2 = function()
  FileFunc1()
end

FileFunc2()
--[[
  [Lua:local] FileFunc2 (@.\test.lua:98)
  [Lua:upvalue] FileFunc1 (@.\test.lua:95)
]]

FileFunc3() = function()
end

local FileFunc4() = function()
  FileFunc3()
end

FileFunc4()
--[[
  [Lua:local] FileFunc4 (@.\test.lua:666666)
  [Lua:global] FileFunc3 (@.\test.lua:108)
]]

load("return 1")()
--[[
  [C:global] load (=[C]:-1)
  [main:] nil (return 1:0)
]]

local obj = setmetatable({}, {__index = { Func1 = function() end } })
obj:Func1()
--[[
  [C:global] setmetatable (=[C]:-1)
  [Lua:method] Func1 (@.\test.lua:127)
]]

具体含义归纳总结

  • "": 通过load()等函数执行的代码块
  • "for iterator": 迭代器
  • "metamethod": meta方法
  • "field": 通过
    M.FuncName()
    形式调用的函数
  • "method": 通过
    M:FuncName()
    形式调用的函数
  • "global": 调用global值
  • "local": 调用local值
  • "upvalue": 调用upvalue值

应用

""正常来说是没太大意义的,load()执行的语句一般只有调试会用,不需要对调试代码本身进行分析
"global"可用于查找没有写类似
local print = print
的文件
"local"可用于查找闭包函数 (还需要加更多判断,来和直接写在文件内的调用进行区分,但如果根据短期内调用频率进行统计的话,直接判断"local"就足够了)

兴趣是最好的老师,
HelloGitHub
让你对编程感兴趣!

简介

HelloGitHub
分享 GitHub 上有趣、入门级的开源项目。

https://github.com/521xueweihan/HelloGitHub

这里有实战项目、入门教程、黑科技、开源书籍、大厂开源项目等,涵盖多种编程语言 Python、Java、Go、C/C++、Swift...让你在短时间内感受到开源的魅力,对编程产生兴趣!


以下为本期内容|每个月
28
号更新

C 项目

1、
audacity
:一款免费、开源的音频编辑工具。该项目是最受欢迎的开源音频编辑软件之一,它拥有容易上手的界面,提供了音频的编辑和录制功能,支持多语言、录音、多音轨混音以及杂音消除等特性,适用于 Windows、macOS、GNU/Linux 操作系统。来自
@ziming012
的分享

2、
VeraCrypt
:一款开源的磁盘加密软件。该项目是基于知名、已停止维护的 TrueCrypt 开发,修复了已知的许多漏洞和安全问题。经过多年的迭代,VeraCrypt 的功能更加强大。它支持动态加密系统分区、硬件加速、隐藏加密容器、多重认证等功能,适用于 Windows、Linux 和 macOS 平台,提供了跨平台的硬盘加密开源解决方案。

C# 项目

3、
Jackett
:一个支持磁力资源聚合搜索的工具。该项目能够将多个私有和公共的 BT 站点转化为统一的 API,并提供了一个简易的 Web 页面,方便统一管理搜索结果和下载任务。

C++ 项目

4、
endless-sky
:一款 2D 太空交易和战斗游戏。这是一款免费、开源的太空探索类游戏。玩家将扮演一位小型宇宙飞船的舰长,在沙盒式的太空环境中展开探险。通过做任务、运送乘客或货物、护航、交易或掠夺敌方飞船,玩家可以赚取金钱,进而购买更强大的飞船并升级武器与引擎,探索更广阔的太空。游戏对硬件配置要求低,支持 Windows、Linux 和 macOS 平台。

5、
Hyprland
:一个灵活、强大的 Wayland 合成器。这是一个高度可定制的动态平铺 Wayland 合成器,用于 Linux 系统的自定义桌面环境。Wayland 是新一代的 Linux 桌面后端服务器协议。该项目提供了多应用程序窗口管理、自动调整、切换和切分窗口的功能。它还支持多显示器设置、自定义外观和丰富的插件扩展。

6、
images
:一个缓存和调整图像尺寸的服务。这个项目是用 C++ 编写的图像处理服务,使用了 Nginx、libvips 和 Cloudflare 等技术。它具备调整图像大小和加速访问的功能,支持多种图像格式,包括 JPEG、PNG、BMP、GIF、TIFF、WebP、PDF 和 SVG 等。来自
@孤胆枪手
的分享

<!-- 源图标地址:wsrv.nl/lichtenstein.jpg -->
<img src="//wsrv.nl/?url=wsrv.nl/lichtenstein.jpg&w=300&h=300">

7、
Shell
:一款强大的 Windows 上下文菜单管理工具。这项目是一个用于管理 Windows 文件资源管理器上下文菜单的程序。简单来说,就是扩展了 Windows 右键菜单的功能。该工具免费、开源、无广告、轻巧,支持所有文件系统对象,如文件、文件夹、桌面和任务栏。它提供了一系列提升效率的功能,包括拷贝文件地址、快速打开目录、终端打开、自定义外观以及复杂的嵌套菜单等。

CSS 项目

8、
hyperui
:免费的 Tailwind CSS 组件集合。该项目提供了一系列适用于网站、营销和电商等网站的免费 Tailwind CSS 组件。这些组件支持深色模式、移动端适配和 LTR,复制代码即可使用。

Go 项目

9、
besticon
:获取网站 favicon 图标的服务。该服务使用 Go 语言编写,用于获取目标网站 favicon.ico 地址。它特别适用于导航类网站,因为它可以很方便地从源站点上获取图标,即使在找不到图标的情况下,也会返回一个站点名称首字母的灰色图标。来自
@Liang INX
的分享

10、
decimal
:解决小数精度问题的 Go 库。该项目旨在解决浮点数类型在计算过程中,可能出现的精度丢失问题。它提供了一个名为 Decimal 的数据类型,支持常见的加法、减法、乘法和除法运算,保证结果不会丢失精度,同时还提供了四舍五入、取整和序列化等功能。

package main

import (
	"fmt"
	"github.com/shopspring/decimal"
)

func main() {
	price, err := decimal.NewFromString("136.02")
	if err != nil {
		panic(err)
	}

	quantity := decimal.NewFromInt(3)

	fee, _ := decimal.NewFromString(".035")
	taxRate, _ := decimal.NewFromString(".08875")

	subtotal := price.Mul(quantity)

	preTax := subtotal.Mul(fee.Add(decimal.NewFromFloat(1)))

	total := preTax.Mul(taxRate.Add(decimal.NewFromFloat(1)))

	fmt.Println("Subtotal:", subtotal)                      // Subtotal: 408.06
	fmt.Println("Pre-tax:", preTax)                         // Pre-tax: 422.3421
	fmt.Println("Taxes:", total.Sub(preTax))                // Taxes: 37.482861375
	fmt.Println("Total:", total)                            // Total: 459.824961375
	fmt.Println("Tax rate:", total.Sub(preTax).Div(preTax)) // Tax rate: 0.08875
}

11、
gocv
:基于 OpenCV 的 Go 语言计算机视觉库。OpenCV 是一个开源、跨平台的计算机视觉库,多用于做图像处理、视频采集和分析。该项目是 OpenCV 的 Go 语言封装库,让开发者可以使用 Go 语言调用 OpenCV 库,具有支持多平台、OpenCV 4+ 和 GPU 硬件加速等特性。

package main

import (
	"gocv.io/x/gocv"
)

func main() {
	// 打开摄像头
	webcam, _ := gocv.OpenVideoCapture(0)
	// 新建 GUI 窗口
	window := gocv.NewWindow("Hello")
	img := gocv.NewMat()
	// 显示视频
	for {
		webcam.Read(&img)
		window.IMShow(img)
		window.WaitKey(1)
	}
}

12、
goreleaser
:快速、优雅地发布 Go 应用。这是一个 Go 项目打包、签名和发布的工具,支持自动发布到 GitHub、GitLab 和 Gitea 平台、创建 Docker 镜像、Linux 软件包和 Homebrew 等功能,可在本地运行也支持 CI/CD 系统,但免费版不支持构建 macOS 和 Windows 安装包。

13、
termdash
:一个跨平台、可定制的终端仪表盘。该项目提供了丰富的终端小部件,如按钮、进度条、图表等,可用于创建各种交互式终端工具。它支持 UTF-8 编码、鼠标事件和自定义布局等功能,能够快速构建出拥有好看界面的终端应用。

Java 项目

14、
winlator
:在 Android 上运行 Windows 游戏的模拟器。这是一个 Android 应用,可以让你使用 Wine 和 Box86/Box64 来运行 Windows 应用和游戏,实现在手机上畅玩各种经典的 PC 游戏。

JavaScript 项目

15、
excalidraw
:手绘风格的白板 Web 应用。这是一款完全免费、开源的基于无限画布的白板 Web 应用,用户可以在上面创建手绘风格的作品。支持包括中文在内的多种语言,提供了自由绘制、多种工具、导出 PNG、实时协作、共享链接、自动保存等功能。

16、
jsoneditor
:在线的 JSON 编辑器。该项目是一个基于 Web 的 JSON 编辑器,可用于查看、编辑、格式化和验证 JSON。它支持树形编辑器、代码编辑器和纯文本等模式,不仅可以直接在线使用,还可作为组件集成到项目中。

17、
reactjs-interview-questions
:前端面试 React 高频问题和答案。该项目涵盖了一系列 React 相关的面试问题和答案,内容涉及基础知识、组件、状态管理、生命周期以及性能优化等方面。

18、
super-productivity
:一款先进的待办事项列表应用。超级生产力是一款用 TypeScript 开发的高级 TODO 应用,旨在帮助用户规划任务和管理待办事项,培养健康高效的习惯。它开源、免费、无需注册,支持与 Jira、GitHub、GitLab 等第三方平台集成,可即时收到任务变动的通知。除了可在线使用的 Web 网页版,还提供了 Windows、Linux、macOS、iOS 和 Android 客户端。

19、
tsparticles
:立刻给网站安排上动画背景的库。该项目可用于创建高度可定制的 JavaScript 粒子效果,比如雪花、彩带和烟花效果等。虽然它是一个独立库、不依赖其他库或框架,但项目内提供了 React、Vue、Angular、Svelte、jQuery 等框架的现成组件,以便于快速集成到项目中。

Kotlin 项目

20、
MaterialFiles
:一款开源的 Android 文件管理器。该项目是一个安卓文件管理器,采用 Kotlin 开发,并遵循 Material Design 设计规范。它轻量、简洁并且安全,支持 root 权限查看和管理文件、压缩/解压文件、远程查看服务器上的文件等功能,适用于 Android 5.0+ 系统。

Python 项目

21、
cachetools
:实用的 Python 缓存装饰器。这是一个 Python 的缓存库,它提供了多种缓存算法的数据类型和 Python 标准库的 @lru_cache 函数装饰器的变种,该库适用于避免重复计算、加速结果返回以及减少重复请求等场景。

from cachetools import cached, LRUCache, TTLCache

# speed up calculating Fibonacci numbers with dynamic programming
@cached(cache={})
def fib(n):
    return n if n < 2 else fib(n - 1) + fib(n - 2)

# cache least recently used Python Enhancement Proposals
@cached(cache=LRUCache(maxsize=32))
def get_pep(num):
    url = 'http://www.python.org/dev/peps/pep-%04d/' % num
    with urllib.request.urlopen(url) as s:
        return s.read()

# cache weather data for no longer than ten minutes
@cached(cache=TTLCache(maxsize=1024, ttl=600))
def get_weather(place):
    return owm.weather_at_place(place).get_weather()

22、
Ciphey
:自动解密/解码和破解各种加密算法的工具。使用该项目时,你只需输入加密的文本,无需提供具体的加密类型,它就可以在 3 秒或更短的时间内自动解密大多数的加密文本。这个项目支持 30 多种常见的加密方式,包括二进制、base64、哈希等。

23、
music-tag-web
:编辑歌曲文件元数据的 Web 应用。这款音乐标签编辑器提供了编辑歌曲标题、专辑、艺术家、歌词、封面等信息的功能。它支持多种音频格式,包括 FLAC、APE、WAV、AIFF、MP3 和 MP4 等。此外,它还提供了自动批量修改和整理音乐文件、歌词翻译、手机端访问等实用功能。来自
@xier
的分享

24、
sqlite-web
:基于 Web 的 SQLite 数据库管理工具。这是一个用 Flask 和 peewee 编写的 SQLite 数据库 Web 管理平台。它安装简单、启动也非常方便。该项目提供了一个简单易用的界面,以及实用的 SQLite 数据管理功能,包括创建/删除表、索引、数据导入/导出、排序、SQL 查询等功能。

25、
toolong
:好用的终端日志文件处理工具。这是一个用于查看、追踪、合并和搜索,日志/JSON 长文件的命令行工具。它提供了高亮显示和实时追踪日志的功能,支持快速打开 GB 级的文件,并能根据时间戳自动合并日志文件。

Ruby 项目

26、
judge0
:开源的在线代码执行系统。该项目是用 Ruby 开发的在线代码执行系统,它安装简单、功能强大,支持 60 多种编程语言,可以设置代码执行时间和内存限制,并提供详细的执行结果,包括编译错误、运行错误和输出结果等信息。可用于构建竞赛编程、在线代码编辑和面试等平台。

Rust 项目

27、
czkawka
:多功能文件清理工具。该项目是用 Rust 编写的,用于查找和清理重复文件、空文件夹以及相似图片等文件。它免费、开源且无广告,具有快速、跨平台和多语言等特点。使用这个工具,可以轻松地清理电脑上的无用文件,释放电脑的存储空间。

28、
meilisearch
:一款轻量级的 Rust 搜索引擎。该项目是采用 Rust 编写的轻量且快速的搜索引擎。它具有开箱即用、易于维护和搜索速度快等特点,提供了实时搜索、容错纠正、排序、同义词等功能,支持包括中文在内等的多种语言。

client = meilisearch.Client('http://localhost:7700', 'masterKey')

client.index('movies').add_documents([
  { 'id': 1, 'title': 'Carol' },
  { 'id': 2, 'title': 'Wonder Woman' },
  { 'id': 3, 'title': 'Life of Pi' },
  { 'id': 4, 'title': 'Mad Max: Fury Road' },
  { 'id': 5, 'title': 'Moana' },
  { 'id': 6, 'title': 'Philadelphia'}
])

29、
MessAuto
:Mac 上的自动提取短信和邮箱验证码工具。这款软件是采用 Rust 开发的,专为 macOS 平台设计的自动提取短信和邮箱验证码到剪贴板的工具。它具有免费、小巧、适用于任何应用的特点,其工作原理是监听邮件(Mail)和短信(iMessage)应用程序的消息,自动提取消息中的验证码,并将其存储到剪贴板中,运行后只有一个安静的任务栏托盘图标。

Swift 项目

30、
Minesweeper-Desktop
:macOS 桌面版扫雷游戏。该项目是一个用 Swift 开发的 macOS 扫雷游戏,它提供了原汁原味的扫雷体验,保留了经典的外观、自定义玩法和操控方式。来自
@孤胆枪手
的分享

31、
Rectangle
:macOS 上的窗口管理工具。该项目是 Swift 编写的窗口管理工具,基于 Spectacle 实现。它可通过键盘快捷键在 macOS 上快速移动窗口和调整窗口大小,适用于 macOS 10.15+、Intel 和 Apple 芯片。

其它

32、
CorsixTH
:主题医院游戏开源复刻版。该项目是采用 Lua 和 C++ 重新制作的经典模拟经营游戏《主题医院》,它在保留了原游戏经典玩法的基础上,增加了对现代操作系统(Windows、Linux 和 macOS)、中文语言以及高分辨率的支持。需要注意的是安装游戏后,无法立即运行,因为游戏的数据需要单独下载。

33、
foc-wheel-legged-robot
:一个新型结构的双轮腿机器人。该项目包含了制作这款机器人所需的全部资料,包括机械结构设计、电子硬件、算法仿真和源码等,制作的物料成本在 700 元左右。

34、
h5player
:网页播放器增强插件。这是一款浏览器插件,支持网页视频倍速/加速播放、截图、画中画、直播同步和下载等功能,适用于国外各大主流视频网站。

35、
system-design-101
:图文并茂的系统设计入门教程。该项目通过通俗易懂的文字和简洁明了的示意图,讲解系统设计的基础知识以及深层的工作原理的入门级教程。无论你是初学者还是准备面试的程序员,在这里都能有所收获。

36、
wsl2-distro-manager
:WSL 发行版图形管理工具。该项目是一个基于 Flutter 开发的 WSL 管理小工具,它提供了一个友好的图形化界面,让用户可以轻松配置、复制或转换 WSL 实例,免去了繁琐的命令操作。特别适合新手使用,不用再担心把 WSL 折腾坏了。来自
@mtig
的分享

开源书籍

37、
Hypervisor-From-Scratch
:《从零创建虚拟机管理程序》。该项目提供了一个从头开始构建虚拟机监控程序的教程,内容涵盖基本概念、硬件虚拟化的技术细节以及源码等方面。帮助开发者了解虚拟机的工作原理,并一步步构建自己的虚拟机监控程序。

机器学习

38、
llm-viz
:3D 可视化 GPT 大语言模型。该项目通过 3D 可视化的方式,演示了类似 GPT 的大语言模型的工作原理和推理过程。

39、
nn-zero-to-hero
:从零到神经网络高手。这是一门从基础开始的神经网络课程,包含视频、练习和配套源码,帮助初学者初逐步掌握神经网络的基本概念,并通过实例代码来加深理解。

40、
pandas-ai
:数据分析对话化的开源库。该项目将 AIGC 和数据分析相结合,让用户可以通过自然语言向自己的数据进行提问,并获得相应的回答。首先,需要将数据以 pandas 的方式进行导入,然后配置好 OpenAI TOKEN 就可以开始通过对话和绘制图表等方式与数据进行交互,而无需编写代码。

import pandas as pd
from pandasai import SmartDataframe

# Sample DataFrame
df = pd.DataFrame({
    "country": ["United States", "United Kingdom", "France", "Germany", "Italy", "Spain", "Canada", "Australia", "Japan", "China"],
    "gdp": [19294482071552, 2891615567872, 2411255037952, 3435817336832, 1745433788416, 1181205135360, 1607402389504, 1490967855104, 4380756541440, 14631844184064],
    "happiness_index": [6.94, 7.16, 6.66, 7.07, 6.38, 6.4, 7.23, 7.22, 5.87, 5.12]
})

# Instantiate a LLM
from pandasai.llm import OpenAI
llm = OpenAI(api_token="YOUR_API_TOKEN")

df = SmartDataframe(df, config={"llm": llm})
df.chat('Which are the 5 happiest countries?')

41、
PhotoMaker
:AI 生成各种风格人类照片的工具。该项目可以通过上传的人物照片,生成任意风格的人物图像,如写实、卡通、艺术等风格,可用于生成别具一格的头像。

最后

感谢参与分​享开源项目的小伙伴们,欢迎更多的开源爱好者来 HelloGitHub 自荐/推荐开源项目。如果你发现了 GitHub 上有趣的项目,就
点击这里
分享给大家伙吧!

本期有你感兴趣的开源项目吗?如果有的话就留言告诉我吧~如果还没看过瘾,可以
点击阅读
往期内容。


那么,下个月
28 号
不见不散,完结撒花

前言

在之前的内容中,我们简要探讨了循环依赖,并指出仅通过引入二级缓存即可解决此问题。然而,你可能会好奇为何在Spring框架中还需要引入三级缓存singletonFactories。在前述总结中,我已经提供了答案,即AOP代理对象。接下来,我们将深入探讨这一话题。

AOP

在Spring框架中,AOP的实现是通过一个名为BeanPostProcessor的类完成的,其中一个关键的BeanPostProcessor就是AnnotationAwareAspectJAutoProxyCreator。值得一提的是,该类的父类是AbstractAutoProxyCreator。在Spring的AOP机制中,通常会使用JDK动态代理或者CGLib动态代理来实现代理对象的生成。因此,如果在某个类的方法上设置了切面,那么最终这个类将需要生成一个代理对象来应用AOP的功能。

一般的执行流程通常是这样的:A类--->生成一个普通对象-->属性注入-->基于切面生成一个代理对象-->将该代理对象存入singletonObjects单例池中。

而AOP可以说是Spring框架中除了IOC之外的另一个重要功能,而循环依赖则属于IOC的范畴。因此,为了让这两个重要功能同时存在于Spring框架中,Spring需要进行特殊处理。

三级缓存

在处理这种情况时,Spring框架利用了第三级缓存singletonFactories。下面我们来看一下关于三级缓存的源代码实现:

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
        // Quick check for existing instance without full singleton lock
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
            singletonObject = this.earlySingletonObjects.get(beanName);
            if (singletonObject == null && allowEarlyReference) {
                synchronized (this.singletonObjects) {
                    // Consistent creation of early reference within full singleton lock
                    singletonObject = this.singletonObjects.get(beanName);
                    if (singletonObject == null) {
                        singletonObject = this.earlySingletonObjects.get(beanName);
                        if (singletonObject == null) {
                            ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                            if (singletonFactory != null) {
                                singletonObject = singletonFactory.getObject();
                                this.earlySingletonObjects.put(beanName, singletonObject);
                                this.singletonFactories.remove(beanName);
                            }
                        }
                    }
                }
            }
        }
        return singletonObject;
    }

首先,singletonFactories中存储的是某个beanName对应的ObjectFactory。在bean的生命周期中,生成完原始对象之后,Spring框架会构造一个ObjectFactory并将其存入singletonFactories中。这个ObjectFactory是一个函数式接口,因此支持Lambda表达式,形式为() -> getEarlyBeanReference(beanName, mbd, bean)。为了更清晰地理解这个过程,我提供一张图片。

image

getEarlyBeanReference

在上述Lambda表达式中,它实际上代表了一个ObjectFactory,执行该Lambda表达式将会调用getEarlyBeanReference方法。下面是getEarlyBeanReference方法的实现:

//AbstractAutowireCapableBeanFactory
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    Object exposedObject = bean;
    if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
        for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
            exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
        }
    }
    return exposedObject;
}

在整个Spring框架中,值得注意的是,只有AbstractAutoProxyCreator这个类在实现getEarlyBeanReference方法时才具有真正的意义。这个类专门用于处理AOP(面向切面编程)。

// AbstractAutoProxyCreator
@Override
public Object getEarlyBeanReference(Object bean, String beanName) {
 Object cacheKey = getCacheKey(bean.getClass(), beanName);
 this.earlyProxyReferences.put(cacheKey, bean);
 return wrapIfNecessary(bean, beanName, cacheKey);
}

那么,getEarlyBeanReference方法的具体操作是什么呢?
首先,它会获取一个cachekey,这个cachekey实际上就是beanName。
接着,它会将beanName和bean(即原始对象)存储到earlyProxyReferences中。
接下来,它会调用wrapIfNecessary方法进行AOP操作,这将生成一个代理对象。

那么,什么时候会调用getEarlyBeanReference方法呢?让我们再次回到循环依赖的场景中。

image

在我上一节的基础上,我增加了两句话,以便更好地理解触发缓存机制以解决AOP代理对象生成的时机。

一旦原始对象通过构造方法生成后,会被存储到三级缓存中,并且会与一个lambda表达式关联。然而,在这个阶段,它并不会被执行。

一旦BBean需要ABean时,系统会首先查看三级缓存以确定是否存在缓存。如果存在缓存,则lambda表达式将会被执行,其代码已在前面展示过。该lambda表达式的目的是将代理对象放入earlySingletonObjects中。需要注意的是,此时代理对象并未被放入singletonObjects中。那么代理对象何时会被放入singletonObjects中呢?

这个时候你可能已经明白了
earlySingletonObjects
的用途。由于只获取了A原始对象的代理对象,这个代理对象并不完整,因为A原始对象尚未进行属性填充。因此,在这种情况下,我们不能直接将A的代理对象放入
singletonObjects
中。因此,我们只能将代理对象放入
earlySingletonObjects
,这样依次类推。

在Spring框架中,在循环依赖场景下,当Bean B创建完成后,Bean A继续其生命周期。在Bean A完成属性注入后,根据其自身逻辑进行AOP操作。此时,我们知道Bean A的原始对象已经经历了AOP处理,因此对于Bean A本身而言,不需要再次进行AOP。那么,如何确定一个对象是否已经经历了AOP呢?

public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
    if (bean != null) {
        Object cacheKey = getCacheKey(bean.getClass(), beanName);
        if (this.earlyProxyReferences.remove(cacheKey) != bean) {
            return wrapIfNecessary(bean, beanName, cacheKey);
        }
    }
    return bean;
}

没错,这个earlyProxyReferences确实提前缓存了对象是否已经被代理过,这样就避免了重复的AOP处理。

举例反证

那么问题来了,当A对象创建时,它可以是原始对象,但当B对象创建时,却成功创建了A的代理对象。然后再回头给A对象进行属性注入和初始化,这些操作似乎与代理对象无关?

这个问题涉及到了 Spring 中动态代理的实现。无论是使用cglib代理还是jdk动态代理生成的代理类,代理时都会将目标对象 target 保存在最终生成的代理 $proxy 中。你可以将代理对象看作是对原始对象地址的一层包装,最终仍然会回到原始对象上。因此,对原始bean的进一步完善实际上也就是对代理对象的完善。

还有一个需要注意的问题,当A创建时,由于earlyProxyReferences缓存的原因,并没有创建代理对象,因此此时A仍然保持为原始对象。我们知道,当bean创建完成后,它将被放入一级缓存中,但如果在此之后被其他对象引用,那不就会出现问题吗?别人引用的都是原始对象了,而不是代理对象,但是请不要着急,因为在实例化之后,有一行代码可以解决这个问题。

if (earlySingletonExposure) {
            Object earlySingletonReference = getSingleton(beanName, false);
            if (earlySingletonReference != null) {
                if (exposedObject == bean) {
                    exposedObject = earlySingletonReference;
                }
......省略代码

在实例化原始对象后,他会首先从三级缓存中检查是否存在缓存对象。这是因为在创建B对象时,已经将A的代理对象放入二级缓存。因此,取出的对象是代理对象。接着,当进行
exposedObject == bean
的比较时,发现它们不相同。因此,以代理对象为准并将其返回。最终,最外层存储的将是代理对象。

protected void addSingleton(String beanName, Object singletonObject) {
    synchronized (this.singletonObjects) {
        this.singletonObjects.put(beanName, singletonObject);
        this.singletonFactories.remove(beanName);
        this.earlySingletonObjects.remove(beanName);
        this.registeredSingletons.add(beanName);
    }
}

总结

在上一个章节中我们提到了今天要讨论三级缓存,让我们根据上面提到的三级缓存内容,做一个详尽的总结:

  • singletonObjects:缓存经过了完整生命周期的bean。

  • earlySingletonObjects缓存了未经过完整生命周期的bean。当某个bean出现循环依赖时,该bean会被提前放入earlySingletonObjects中。如果该bean需要经过AOP,那么代理对象将会被放入earlySingletonObjects;否则,原始对象将被放入其中。然而,无论是代理对象还是原始对象,它们的生命周期都尚未完全结束。因此视为未经过完整生命周期的bean。

  • singletonFactories缓存的是一个ObjectFactory,这个ObjectFactory实际上是一个Lambda表达式。在每个Bean的生成过程中,当原始对象实例化完成后,会提前基于原始对象生成一个Lambda表达式,并将其保存到三级缓存中。这个Lambda表达式可能会被使用,也可能不会。如果当前Bean不存在循环依赖,那么这个Lambda表达式将不会被使用,当前的Bean将按照正常的生命周期执行完毕,并将自身放入singletonObjects中。但是,如果在依赖注入的过程中发现了循环依赖(即当前正在创建的Bean被其他Bean所依赖),则会从三级缓存中取出Lambda表达式,并执行它以获取一个对象,然后将得到的对象放入二级缓存中。需要特别注意的是,如果当前Bean需要AOP处理,则执行Lambda表达式后得到的将是代理对象;否则,直接得到的是原始对象。

当涉及Spring框架中动态代理的实现机制时,除了已经提到的earlySingletonObjects和singletonFactories这两个缓存外,还有一个重要的缓存值得一提,那就是earlyProxyReferences。这个缓存的作用在于记录某个原始对象是否已经进行过AOP(面向切面编程)处理。

至此,整个循环依赖解决完毕。

引言

在Python编程中,经常会遇到需要判断对象是否具有某个属性的情况。这时候就可以用到Python内置函数
hasattr()
。本文将深入探讨
hasattr()
函数的使用方法及其在实际编程中的应用。

语句概览

hasattr()
函数用于检查对象是否具有指定的属性,返回一个布尔值。其语法如下:

hasattr(object, attribute)

object:要检查的对象。
attribute:属性名称,可以是字符串或对象。

函数实例

例1:
检查对象是否具有某个属性

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

person1 = Person("Alice", 30)
print(hasattr(person1, "name"))  # 输出结果为 True
print(hasattr(person1, "gender"))  # 输出结果为 False

在这个例子中,我们创建了一个 Person 类的实例 person1,然后使用
hasattr()
函数来检查该实例是否具有 name 和 gender 这两个属性。由于 person1 实例具有 name 属性,所以第一个
hasattr()
函数返回 True;而 person1 实例没有 gender 属性,所以第二个
hasattr()
函数返回 False。

例2:
检查模块是否具有某个函数

import math

print(hasattr(math, "sqrt"))  # 输出结果为 True
print(hasattr(math, "power"))  # 输出结果为 False

在这个例子中,我们使用
hasattr()
函数来检查 math 模块是否具有 sqrt 和 power 这两个函数。由于 math 模块具有 sqrt 函数,所以第一个
hasattr()
函数返回 True;而 math 模块没有 power 函数,所以第二个
hasattr()
函数返回 False。

例3:
检查字符串是否具有某个方法

s = "Hello, World!"
print(hasattr(s, "upper"))  # 输出结果为 True
print(hasattr(s, "split"))  # 输出结果为 True
print(hasattr(s, "reverse"))  # 输出结果为 False

在这个例子中,我们使用
hasattr()
函数来检查字符串对象 s 是否具有 upper、split 和 reverse 这三个方法。由于字符串对象 s 具有 upper 和 split 方法,所以前两个
hasattr()
函数返回 True;而字符串对象 s 没有 reverse 方法,所以第三个
hasattr()
函数返回 False。

例4:
检查类是否具有某个静态方法

class MyClass:
    @staticmethod
    def my_static_method():
        pass

print(hasattr(MyClass, "my_static_method"))  # 输出结果为 True
print(hasattr(MyClass, "my_instance_method"))  # 输出结果为 False

在这个例子中,我们使用
hasattr()
函数来检查 MyClass 类是否具有 my_static_method 和 my_instance_method 这两个静态方法。由于 MyClass 类具有 my_static_method 静态方法,所以第一个
hasattr()
函数返回 True;而 MyClass 类没有 my_instance_method 方法,所以第二个
hasattr()
函数返回 False。

例5:
检查实例是否具有特殊方法

class MyClass:
    def __str__(self):
        return "MyClass object"

obj = MyClass()
print(hasattr(obj, "__str__"))  # 输出结果为 True
print(hasattr(obj, "__len__"))  # 输出结果为 False

在这个例子中,我们使用
hasattr()
函数来检查 obj 实例是否具有
str

len
这两个特殊方法。由于 obj 实例具有
str
特殊方法,所以第一个
hasattr()
函数返回 True;而 obj 实例没有
len
方法,所以第二个
hasattr()
函数返回 False。

应用场景

  • 动态调用属性或方法: 可以在运行时根据需要动态地检查对象是否具有某个属性或方法,以决定是否进行相应的操作。
  • 遍历对象属性: 可以通过循环遍历对象的属性,并根据需求进行处理,例如打印出对象的所有属性及其值。

注意事项

  • 要注意对象是否为 None,因为对于 None 对象的任何属性调用都会引发 AttributeError 异常。
  • 由于Python是动态语言,属性和方法可以在运行时动态添加或删除,因此在使用 hasattr() 函数时要考虑对象的状态可能发生变化。

结语
hasattr()
函数是Python中非常实用的工具,能够帮助我们在编程中进行属性和方法的动态检查。合理地使用该函数可以使我们的代码更加灵活、健壮。希望本文能够帮助大家更好地理解和应用
hasattr()
函数。