news 2026/10/15 20:04:35

Nugget 的 Tendies 与 Batter 文件格式详解:PosterBoard 壁纸包与可定制模板的完整开发指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Nugget 的 Tendies 与 Batter 文件格式详解:PosterBoard 壁纸包与可定制模板的完整开发指南
  • 桌面应用
  • 移动开发
  • 音视频

【免费下载链接】Nugget

Unlock the fullest potential of your device

项目地址:https://gitcode.com/gh_mirrors/nug/Nugget
点击查看免费下载

导读

本文以 Nugget 官方文档 documentation.md 为骨架,系统讲解两类驱动 Nugget PosterBoard 壁纸功能的核心文件格式:Tendies 文件(PosterBoard 壁纸/描述符包,.tendies)与Batter 文件(可定制模板包,.batter)。读者将掌握两种文件格式的内部结构、配置清单(config.json)的 Header 与 Option 完整写法、基于nuggetId的 CoreAnimation XML 标识机制,以及 replace / set / remove / picker / bundle_id 五种选项类型的 JSON 规范与底层实现原理,最终能够独立制作、发布属于自己的 Nugget 壁纸与模板文件。


一、Tendies 文件:PosterBoard 壁纸包

Tendies 文件(壁纸包)本质上是存储"将要恢复到 PosterBoard 的文件结构"的压缩归档。Nugget 会读取包内的目录布局,把文件原样还原到设备的 PosterBoard 容器中。依据包内顶层目录的命名,Tendies 文件分为两种格式:

格式顶层文件夹名恢复行为UUID / 壁纸 ID
容器格式(Container)container直接恢复到/var/mobile/Containers/Applications/PosterBoard.app应用容器内,并保持原有文件结构不会随机化
描述符格式(Descriptor)descriptor或descriptors恢复到容器内的描述符(descriptors)目录会随机化,防止冲突

1.1 容器格式(Container)

当包内出现名为container的文件夹时,Nugget 将该文件夹下的内容直接映射到 PosterBoard 应用容器(/var/mobile/Containers/Applications/PosterBoard.app)中,目录结构原样保留。由于不做任何改写,包内descriptor的 UUID 与壁纸 ID 也会原封不动地落到设备上。

需要特别警惕的是:如果容器格式的包内包含PBFPosterExtensionDataStoreSQLiteDatabase.sqlite3这一数据库文件,Nugget 会将其标记为unsafe_container,并在导入时弹出警告,提示"可能需要先重置所有壁纸(在设置中开启 Risky Options)并重新应用,该文件才能正常工作"。这一检测逻辑位于 tendie_file.py:扫描归档条目时若命中该文件名即置位危险标志。

1.2 描述符格式(Descriptor)

当包内顶层文件夹名为descriptor或descriptors时,Nugget 将其内容恢复到设备上的描述符目录。恢复的目标位置与系统版本相关:

  • iOS 17.0 及以上:恢复到61号目录;
  • iOS 16:恢复到59号目录。

这一版本差异在 posterboard_tweak.py 中体现:apply_tweak会按version.startswith("16")决定structure_version = 59,其余情况为61。文档同时指出:如果未来系统目录结构再次变化,Nugget 的后续版本可能会自动处理这一兼容问题。

描述符格式下,每个描述符的UUID 与壁纸 ID 都会被随机化,从而避免与设备上已存在的壁纸发生重叠冲突。随机化在recursive_add中实现:描述符文件夹被重命名为str(uuid.uuid4()).upper()形式的新 UUID,壁纸 ID 则通过randint(9999, 99999)生成,同时会改写以下标识文件以保持一致性(见 posterboard_tweak.py):

  • com.apple.posterkit.provider.descriptor.identifier:直接写入随机化 ID;
  • com.apple.posterkit.provider.contents.userInfo:设置wallpaperRepresentingIdentifier键;
  • Wallpaper.plist:非递归地设置identifier键。
1.2.1 有序描述符(ordered)

如果希望在集合(Collections)中让壁纸按 ID 有序排列,可以在文件夹名中加入ordered字样(例如ordered-descriptors)。此时 Nugget 会为同一目录下的所有描述符生成连续递减的 ID 序列:recursive_add先取一个随机基数r_id,生成sorted([r_id + i for i in range(len(os.listdir(curr_path)))], reverse=True)的列表,逐个分配给每个描述符(见 posterboard_tweak.py)。源码注释解释了原因:"PosterBoard 以逆序按壁纸 ID 排序壁纸",因此连续递减的 ID 能保证壁纸在集合中的展示顺序。

1.2.2 按扩展(Extension)区分恢复目标(Nugget v7.0+)

Nugget v7.0 起,描述符格式可以根据文件夹名中的关键词,将壁纸恢复到不同的 PosterBoard 扩展(extension)下:

文件夹名包含目标扩展(providerId)
video或photosSuggested Photos(com.apple.PhotosUIPrivate.PhotosPosterProvider)
mercuryMercury Poster(com.apple.MercuryPoster)
(不加任何词)Collections(com.apple.WallpaperKit.CollectionsPoster)——默认

对应的扩展识别逻辑位于 posterboard_tweak.py:文件夹名中命中video/photos或mercury时设置对应 provider,否则回落为com.apple.WallpaperKit.CollectionsPoster。恢复路径形如:

/Library/Application Support/PRBPosterExtensionDataStore/{61|59}/Extensions/{extension}/descriptors

文档建议:如果目标是恢复到 Collections,优先采用描述符格式,因为随机化 ID 更安全,且对未来版本更具前瞻性。

1.3 Tendie 文件的识别与限制

在 GUI 层,tendie_file.py 通过扫描归档条目统计描述符数量(descriptor_cnt),并据此选择列表图标:容器格式用shippingbox.svg,单个描述符用photo.svg,多个描述符用photo-stack.svg。

同时 Nugget 对壁纸数量设有硬性限制:verify_tendie要求descriptor_cnt + 已有描述符数量 <= 10(见 posterboard_tweak.py),而 Tendies 页面最多同时导入3个.tendies文件(见 posterboard.py),超出即弹出安全提示。


二、Batter 文件:可定制模板包

Batter 文件(模板)与 Tendies 文件类似,同样存储"要恢复到某个 domain 的文件结构",但多了一层用户可定制能力:Nugget 会把包内Container文件夹中的文件结构恢复出去,并依据一个config.json文件在界面上渲染出可交互的选项(按钮、滑块、复选框、颜色选择器、下拉选择器等),让用户无需手工改文件即可定制壁纸。

对于 PosterBoard 模板,Batter 文件沿用了与 Tendies 相同的container/descriptor/descriptors文件夹格式。除 PosterBoard 之外,Batter 模板通过domain字段还可以作用于任意 AppDomain 甚至系统路径,因此其通用性远超壁纸场景(例如通过 sparserestore 写/var/Managed Preferences等系统位置)。

2.1 模板加载流程(源码视角)

TemplateFile 在加载时会:

  1. 在归档中查找config.json(排除位于 descriptor / container 目录内的同名文件);
  2. 校验options与domain字段是否存在,缺失即抛出PBTemplateException;
  3. 兼容 v5.2 beta 时期的旧写法:若domain == "com.apple.PosterBoard",自动升级为"AppDomain-com.apple.PosterBoard";
  4. 校验format_version:若大于当前格式版本CURRENT_FORMAT = 2(见 template_file.py),提示"该配置需要更新版本的 Nugget";
  5. 按min_version/max_version与当前设备系统版本比对,版本不符时拒绝加载;
  6. 解析resources、previews、banner_text、banner_stylesheet等展示信息;
  7. 逐个实例化options中的选项对象(ReplaceOption/RemoveOption/SetOption/PickerOption/bundle_id)。

三、Batter 文件 Header:config.json 头部信息

config.json的头部携带模板的基础信息,分为必填与可选两类。

3.1 必填字段

字段说明
title操作(模板)的标题,显示在界面上
author作者姓名
domain恢复目标 domain。PosterBoard 模板固定填AppDomain-com.apple.PosterBoard;若要使用 sparserestore 能力,填Sparserestore-<FILEPATH>,例如Sparserestore-/var/Managed Preferences
format_version配置格式的最低版本要求;当前 Nugget 最新版本对应的值为"format_version": "2"
options用户可配置选项列表(格式见下文 Option Format 一节);不需要任何可配置项时写"options": []

其中domain与format_version的强校验逻辑见 template_file.py;Sparserestore-域在 templates_tweak.py 中被特殊处理:恢复路径直接拼接Sparserestore-前缀后的绝对路径,且不再使用 AppDomain 恢复方式。

3.2 可选字段

字段说明
description显示在标题下方的说明文字
min_version模板支持的最低 iOS 版本(如"min_version": "18.0")
max_version模板支持的最高 iOS 版本(如"max_version": "18.0")
resources内嵌资源文件列表(如 banner / preview 图片等),支持通配符
previews预览图片文件名列表;这些文件的路径必须同时出现在resources列表中才会被展示
preview_layout预览图的排布方式,默认"horizontal";合法值:"horizontal"(横向排列)、"stacked"(叠放,多个预览图叠在一起展示)
banner_textbanner 横幅的文本内容
banner_stylesheetbanner 的 Qt 样式表(style sheet)

min_version/max_version可只写其一、都不写或都写。加载时 Nugget 会与设备系统版本做Version()比较,不符合则拒绝应用(见 template_file.py)。

resources的落地逻辑在 template_file.py:使用fnmatch.filter支持通配符匹配,将资源解压到临时目录;如果设置了banner_stylesheet,其中的url(<resource>)引用会被改写为临时文件的实际路径;previews中列出的资源则被登记为预览图。preview_layout为"stacked"时,界面使用QStackedLayout叠放预览图(见 template_file.py)。


四、Option Format:选项通用格式

options数组中每个选项都必须包含三个必备属性:

属性说明
type选项类型(各类型的取值见下文"选项类型"一节)
label在 Nugget 界面中显示的文字
files相对于config.json所在目录(其父目录)的文件路径列表
associated_preview(可选)与该选项关联的预览图名称。对部分选项会产生特殊效果:set(如颜色)会应用视觉效果,remove/picker会隐藏图片;对picker类型,每个子选项都必须带此标签

associated_preview的绑定逻辑见 template_options.py:存在associated_preview且与预览图名称匹配时,该选项获得对应QLabel的引用,remove选项通过显隐预览图实时反馈效果(见 remove_option.py)。

4.1 标识符(Identifiers):用 nuggetId 精确改写 .caml

为了告诉 Nugget 需要修改.caml文件中的哪些属性,你可以在目标 XML 标签上添加一个特殊的nuggetId属性,赋值为任意合法字符串(不一定是数字),例如:

<CALayer nuggetId="1" id="#1" allowsEdgeAntialiasing="1" allowsGroupOpacity="1" bounds="0 -28 520 563" contentsFormat="RGBA8" cornerCurve="circular" hidden="0" name="Thing" position="195 365" transform="" zPosition="442"> <sublayers/> </CALayer>

注意:添加nuggetId需要你用文本编辑器手动修改.caml文件。随后在config.json的选项里,通过identifier标签指定这个字符串值即可定位到该 XML 节点。files中的路径应直接指向.caml文件,而不是.ca文件。

如果你希望使用 CoreAnimation 原生的id属性(即 XML 中的id标签)作为定位依据,可以在 JSON 中设置use_ca_id为true。

底层实现位于 xml_handler.py:set_xml_values会根据use_ca_id选择idKey为"id"或"nuggetId",然后通过 XPath//*[@<idKey>='<id>']找到所有匹配节点并写入属性值;当use_ca_id为true时,还会额外处理targetId指向的节点。布尔值会被自动转为0/1整数写入。删除节点时则使用delete_xml_value(见 xml_handler.py)。

4.1.1 偏移方程(nuggetOffset)

你还可以在.caml属性上添加nuggetOffset标签,使 Nugget 在应用时将用户的输入值按数学方程偏移后写入。该标签的格式类似数学表达式,其中x代表用户输入的单值;对于多值属性(如位置),可用x、y、z、a分别代表各分量。例如:

<CALayer nuggetId="1" nuggetOffset="(2 * x + 10), (3 * y + 15)" id="#1" allowsEdgeAntialiasing="1" allowsGroupOpacity="1" bounds="0 -28 520 563" contentsFormat="RGBA8" cornerCurve="circular" hidden="0" name="Thing" position="195 365" transform="" zPosition="442"> <sublayers/> </CALayer>

规则要点:

  • 每个分量之间用逗号分隔;
  • 使用nuggetOffset必须同时有nuggetId(即该标签依赖nuggetId定位);
  • 底层parse_equation(见 xml_handler.py)会把用户输入按空格拆分为x/y/z/a数组,对每个逗号分隔的方程做求值,再以空格连接回写。

注意:从源码结构看,偏移方程目前仅在第一个 key上生效(xml_handler.py中留有 "TODO: Allow offsets for more than just the first value" 注释)。


五、选项类型(Option Types)

5.1 Replace Files(替换文件)

"type": "replace"
属性说明
button_label(可选)导入按钮上显示的文字
allowed_files允许导入的文件类型与扩展名,遵循 QT 过滤器格式,如"Image Files (*.png)"
required是否为必选项。若为true而用户未选择文件,应用模板时会直接报错

界面表现为"导入按钮 + 已选文件路径"(见 replace_option.py):默认按钮文字为Import {allowed_files},点击后弹出QFileDialog选择文件;选择后按钮变为 "Remove Selected File" 并可撤销。应用时(见 replace_option.py),用户所选文件的内容会被逐字节写入files列表中的每一个目标文件(支持通配符,通过glob.glob(..., recursive=True)展开);若required为真且未选文件,则抛出NuggetException。

5.2 Set Values(设置属性值)

"type": "set"
属性说明
identifierXML 文件中的标识(对应nuggetId的值)
use_ca_id(可选)是否改用 CoreAnimation 的id而非nuggetId
key要修改的 XML 属性名(key/tag)
default_value(可选)输入的默认值
value_type(可选)值的类型,合法值:"integer"、"float"、"string"
setter_type用户看到的输入控件类型,合法值:"textbox"、"slider"、"toggle"、"color_picker"

Slider(滑块)专属选项:

属性说明
min_value允许输入的最小值(slider必填,其他控件可选)
max_value允许输入的最大值(slider必填,其他控件可选)
step(可选)滑块每次变化的间隔

Toggle(开关)专属选项:

属性说明
inverted(可选)为true时,写入文件的值取反
toggle_off_value(可选)开关关闭时写入的值(用于非布尔类型)
toggle_on_value(可选)开关打开时写入的值

Color Picker(颜色选择器)专属选项:

属性说明
sets_opacity(可选)是否同时写入透明度;若default_value中提供了 4 个值(RGBA),会自动推断为true

补充细节:

  • 值在.caml中是什么格式,JSON 里就应保持什么格式;如果是浮点数,在 Nugget 中仍需带引号,即"1.0";
  • nuggetOffset方程的用法见上文"偏移方程"一节;
  • identifier是必填属性(见 set_option.py)。

实现要点(set_option.py):

  • slider的min_value/max_value为必填;若value_type为"float",内部会按×1000的整数刻度驱动QSlider(convert_float),并用correct_slider_pos把滑块位置校正到step的整数倍(见 set_option.py);
  • toggle默认值为false;应用时若inverted为真则取反,再按toggle_on_value/toggle_off_value映射(见 set_option.py);
  • color_picker从default_value解析颜色:支持"r g b"或"r g b a"四种分量,自动识别 alpha;界面使用透明棋盘底图叠上当前色(get_stylesheet生成rgba(...)样式),点击弹出QColorDialog;
  • 应用时根据目标文件类型分支处理(见 set_option.py):
    • .caml/.xml:调用set_xml_value写入属性;若sets_opacity且值为QColor,则同时写入opacity键;
    • .plist:调用write_plist_value写入键值;
    • 其他纯文本文件:直接对文件内容做字符串替换。

5.3 Remove Files / Values(删除文件或属性)

"type": "remove"
属性说明
inverted(可选)为true时,仅在复选框未勾选时才删除文件
default_value(可选)复选框的初始状态(默认false)
identifier(可选)XML 文件中的标识;只有需要删除属性或图层时才填
use_ca_id(可选)是否使用 CoreAnimationid而非nuggetId

label属性会直接作为复选框的文字显示(见 remove_option.py)。

应用逻辑(remove_option.py):当(inverted 且未勾选)或(非 inverted 且勾选)时触发删除——若指定了identifier,调用delete_xml_value删除 XML 中对应属性或节点;否则删除files中的文件(目录用rmtree,文件用os.remove)。

5.4 Picker(选择器)

"type": "picker"
属性说明
options选择器中展示的子选项列表,每个子选项包含一个label和一组files
allow_multiple_selection(可选)是否允许多选
rename(可选)是否将用户选中的文件重命名(allow_multiple_selection为true时不生效)
names(可选,rename为true时必填)重命名后的新文件名列表,顺序必须与options中files的顺序一致
default_value(可选)默认选中的下标,从 0 开始,如"default_value": 0;多选时改用"default_values": [0, 1, 2]

行为约定:当用户从选择器中选中某个选项后,应用时其他所有未被选中的选项对应的文件都会被删除(见 picker_option.py)。因此 picker 适合"多选一 / 多选若干"的互斥场景,例如在几套背景 / 前景素材中挑选一套。

界面层:单选使用QComboBox,多选使用 Nugget 自带的MultiComboBox组件(见 picker_option.py)。应用时(picker_option.py):先删除未选中子选项的所有文件,再按names列表将选中文件重命名(目录用shutil.move,文件用os.rename)。若子选项带有associated_preview,预览图会随选择实时显隐(update_preview)。

5.5 Bundle Id(应用包 ID)

"type": "bundle_id"

属性:无。

该选项仅对AppDomain-前缀的模板生效:它允许用户在界面中输入自定义的 App Bundle ID,模板随后恢复到该应用容器中。默认值取自domain字段中AppDomain-之后的部分(见 template_file.py 与 templates_tweak.py:应用时若用户修改了 bundle id,domain 会被替换为AppDomain-{新ID})。

注意:修改 Bundle ID 后,恢复时以相同名字开头的文件也会被一并改写(文档原话强调),即对路径中前缀匹配的应用容器文件生效,使用前需确认影响范围。


六、完整示例:一个典型的 config.json

以下为官方文档给出的完整config.json示例,覆盖了 replace、remove(普通 / 反选 / 删属性)、picker(带 rename)与 set(slider / toggle)全部类型:

{ "title": "My Batter", "description": "I love Nugget", "author": "lemin", "format_version": "2", "domain": "AppDomain-com.apple.PosterBoard", "options": [ { "type": "replace", "label": "Background Image", "button_label": "Select Image", "allowed_files": "Image Files (*.png)", "required": true, "files": ["descriptors/UUID/version/1/bg/assets/image.png", "descriptors/UUID/version/1/fg/assets/image2.png"] }, { "type": "remove", "label": "File Thing Visible", "files": ["descriptors/UUID2/version/1/bg/assets/file1.png"] }, { "type": "remove", "label": "Include collection item", "files": ["descriptors/UUID3"], "inverted": true, "default_value": true }, { "type": "picker", "label": "Choose Things", "rename": true, "names": ["descriptors/UUID/background.ca", "descriptors/UUID/foreground.ca"], "options": [ { "label": "Option 1", "files": ["descriptors/UUID/bg1.ca", "descriptors/UUID/fg1.ca"] }, { "label": "Option 2", "files": ["descriptors/UUID/bg2.ca", "descriptors/UUID/fg2.ca"] }, { "label": "Option 3", "files": ["descriptors/UUID/bg3.ca", "descriptors/UUID/fg3.ca"] } ] }, { "type": "remove", "label": "Show Object", "files": ["descriptors/UUID/version/1/fg.ca/main.caml"], "inverted": true, "default_value": true, "identifier": "1" }, { "type": "set", "label": "Animation Duration", "files": ["descriptors/UUID/version/1/fg.ca/main.caml"], "setter_type": "slider", "min_value": 0.5, "max_value": 3.0, "step": 0.1, "default_value": 1.0, "key": "duration", "identifier": "2" }, { "type": "set", "label": "Flip Geometry", "files": ["descriptors/UUID/version/1/fg.ca/main.caml"], "setter_type": "toggle", "inverted": false, "default_value": false, "key": "geometryFlipped", "identifier": "3" } ] }

示例解读:

  • "format_version": "2"与"domain": "AppDomain-com.apple.PosterBoard"是当前版本的标准头部;
  • replace选项要求用户必须选择一张 PNG 图片(required: true),并同时替换背景与前景两张图片;
  • 第二个remove为"反选删除":复选框默认勾选(default_value: true),勾选时不删除(inverted: true),取消勾选才删除整个descriptors/UUID3目录;
  • picker三选一,选中的.ca文件会被重命名为background.ca/foreground.ca;
  • set选项分别用 slider 修改duration属性、用 toggle 修改geometryFlipped属性,均通过nuggetId("2"、"3")定位.caml中的节点。

七、从文件到设备:应用链路速览

理解了两类文件的格式之后,可以把"打包 → 导入 → 应用"的完整链路串起来(均可在仓库源码中验证):

  1. 打包:Tendies 包内按container/descriptor/descriptors组织目录;Batter 包额外携带config.json(以及可选的resources预览图 / banner 素材)。
  2. 导入解析:TendieFile 识别格式与描述符数量;TemplateFile 校验头部字段并实例化全部选项。
  3. 界面交互:TemplateFile.create_ui 依据resources/previews/banner_text/banner_stylesheet渲染横幅、预览图与各选项控件;bundle_id类型额外生成 Bundle ID 输入框。
  4. 应用生成:PosterboardTweak.apply_tweak 先按系统版本选定structure_version(61 / 59),解压 tendies 与模板,再通过recursive_add递归遍历:容器格式原样映射,描述符格式随机化 UUID 与 ID、按文件夹名路由到对应扩展;set/remove选项在此过程中调用 xml_handler.py 完成.caml改写。
  5. 恢复:最终每个文件被包装为 FileToRestore,由AppDomain-com.apple.PosterBoard(或 sparserestore 路径)恢复机制写入设备。

Tendies 与 Batter 两类文件共同构成了 Nugget PosterBoard 生态的开放文件规范:前者解决"壁纸包怎么分发",后者解决"壁纸包怎么被用户定制"。理解了这两套格式,你就可以像社区壁纸作者一样,产出既美观又可交互的 Nugget 壁纸与模板。


附:仓库相关文件索引

  • 官方格式文档:documentation.md
  • Tendie 解析:src/tweaks/posterboard/tendie_file.py
  • 模板解析与 UI:src/tweaks/posterboard/template_file.py
  • 选项基类与类型枚举:src/tweaks/posterboard/template_options/template_options.py
  • 四种选项实现:replace_option.py、set_option.py、remove_option.py、picker_option.py
  • PosterBoard 应用逻辑:src/tweaks/posterboard/posterboard_tweak.py
  • XML(.caml)改写引擎:src/controllers/xml_handler.py
  • PosterBoard 页面与导入限制:src/gui/pages/tools/posterboard.py
  • 恢复文件模型:src/restore/restore.py
  • 桌面应用
  • 移动开发
  • 音视频

【免费下载链接】Nugget

Unlock the fullest potential of your device

项目地址:https://gitcode.com/gh_mirrors/nug/Nugget
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/15 20:04:09

Hive行级更新实战:ACID事务表配置与合并调优指南

1. 为什么行级更新在数据仓库里是个“老大难”做数据仓库这行的朋友&#xff0c;大概率都遇到过这样的场景&#xff1a;业务方跑过来问&#xff0c;“昨天那批订单里有几条状态填错了&#xff0c;能不能直接改一下&#xff1f;”或者“用户画像表里某个标签打错了&#xff0c;能…

作者头像 李华
网站建设 2026/10/15 20:03:51

AI论文平台实测:千笔写作工具与云笔AI怎么选

“AI论文平台”这个关键词&#xff0c;最近一年我身边的同学基本都刷到过。不过真要问哪个好用&#xff0c;网上大多是广告软文&#xff0c;真正把两个工具放在一起掰开揉碎对比的&#xff0c;还真不多。前阵子有个专科的朋友私信我&#xff0c;说毕业论文只剩一个月&#xff0…

作者头像 李华
网站建设 2026/10/15 20:02:58

STM32F103定时器触发ADC+DMA实现工业级高精度周期采样

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/15 20:02:08

从 Desk2ONVIF 看 libuvcpp + AI 的真实生产力:一个验证案例

从 Desk2ONVIF 看 libuvcpp AI 的真实生产力&#xff1a;一个验证案例 上一篇聊了 REA 逆向工程与 libuvcpp 结合的可能性——用 AI 理解需求&#xff0c;用框架快速构建实现。当时那是一个构想。 现在&#xff0c;这个构想有了一个真实的落地案例&#xff1a;Desk2ONVIF&…

作者头像 李华
网站建设 2026/10/15 20:00:59

高原山地监测适用大气压力传感器,宽量程气压测量

高原、山地区域开展环境与气象监测&#xff0c;现场海拔起伏大&#xff0c;大气压力和平原区域存在明显差异&#xff0c;普通气压传感设备难以适配这类复杂海拔环境。在这类项目选型阶段&#xff0c;需要挑选适配高原山地工况、支持宽量程气压测量的大气压力传感器&#xff0c;…

作者头像 李华
网站建设 2026/10/15 20:00:43

大厂网络面试高频考点全解析:TCP、HTTP与场景排查

1. 为什么网络知识成了面试的“必答题”但凡面过几家像样的公司&#xff0c;你大概率会发现一个规律&#xff1a;越是大厂或者基础架构团队&#xff0c;越喜欢在计算机网络这块往深了问。不是他们故意刁难人&#xff0c;而是网络这东西太底层了——你写业务代码可能感觉不到它的…

作者头像 李华