解析Protobuf文件并转为JSON格式

我们知道 Protobuf 文件可以减少体积,方便存储和传输,但在获取文件后,我们还需要将其转换为 JSON 格式,以适配应用进行处理数据。 下面的代码是在微信小程序中使用,将 pb 文件转换为 JSON,然后再使用。 // 小程序根目录执行安装(需开启 npm 支持) // npm install protobufjs // 创建 proto 文件 /proto/item.proto /* syntax = "proto3"; message DataItem { string id = 1; string name = 2; string kw = 3; bool lock = 4; string category = 5; string label = 6; int32 grade = 7; } message DataArray { repeated DataItem items = 1; } */ // 在 app.js 中引入 const protobuf = require('protobufjs'); // 页面 JS 文件 Page({ data: { items: [] }, onLoad() { this.loadProtoData(); }, async loadProtoData() { try { // 1. 下载二进制文件 const { tempFilePath } = await this.downloadFile(); // 2. 读取文件内容 const arrayBuffer = await this.readFile(tempFilePath); // 3. 加载 Proto 定义 const root = await protobuf.load('/proto/item.proto'); const DataArray = root.lookupType('DataArray'); // 4. 解析数据 const decoded = DataArray.decode(new Uint8Array(arrayBuffer)); // 5. 转换格式 const result = decoded.items.map(item => ({ id: item.id || '', name: item.name || '', kw: item.kw || '', lock: typeof item.lock === 'boolean' ? item.lock : false, category: item.category || '', label: item.label || '', grade: item.grade || 1 })); this.setData({ items: result }); } catch (err) { console.error('解析失败:', err); } }, downloadFile() { return new Promise((resolve, reject) => { wx.downloadFile({ url: 'https://your-domain.com/data.bin', success: resolve, fail: reject }); }); }, readFile(tempFilePath) { return new Promise((resolve, reject) => { wx.getFileSystemManager().readFile({ filePath: tempFilePath, encoding: 'binary', success: res => resolve(res.data), fail: reject }); }); } }); 当文件非常大时,解析速度会有点慢啊,为了更进一步的提高速度,我们可以使用 WebAssembly,这里是一个 Wasm 示例: ...

2026-01-05 · 4 min · Eagle

删除JSON数组对象中的某个字段

我需要将 JSON 字符串中的一个对象属性删除,如果手动删除,又累又无聊。 一个 Python 脚本搞定。 import json def remove_filename_and_save(input_path, output_path): """ 从JSON数组中移除所有对象的filename属性并保存到新文件 Args: input_path: 输入JSON文件路径 output_path: 输出JSON文件路径 """ try: # 读取输入JSON文件 with open(input_path, 'r', encoding='utf-8') as f: data = json.load(f) # 检查是否是数组 if not isinstance(data, list): print("错误:输入JSON不是数组对象") return False # 处理每个对象 processed_data = [] for item in data: if isinstance(item, dict): # 创建新对象,排除filename属性 new_item = {k: v for k, v in item.items() if k != 'filename'} processed_data.append(new_item) else: processed_data.append(item) # 非字典元素保持不变 # 写入输出文件 with open(output_path, 'w', encoding='utf-8') as f: json.dump(processed_data, f, ensure_ascii=False, indent=2) print(f"成功处理并保存到 {output_path}") return True except FileNotFoundError: print(f"错误:输入文件 {input_path} 未找到") return False except json.JSONDecodeError: print(f"错误:输入文件 {input_path} 不是有效的JSON格式") return False except Exception as e: print(f"处理文件时出错: {e}") return False if __name__ == "__main__": # 文件路径配置 input_json = "/data/data.json" # 输入文件路径 output_json = "/data/data-temp.json" # 输出文件路径 # 执行处理 if remove_filename_and_save(input_json, output_json): print(f"操作成功完成,结果已保存到 {output_json}") else: print("操作失败,请检查错误信息") 可以基于该脚本进行扩展。

2026-01-05 · 1 min · Eagle

使用towxml组件在小程序中渲染Markdown内容

今天我们要讲到含金量非常高的技术了,将 Markdown 内容渲染为小程序页面,这是豆子碎片文章页面。 在微信小程序中,渲染 Markdown 内容可以使文章展示更加美观和统一。本文将通过一个完整的示例,演示如何从服务器下载 Markdown 内容,并使用 towxml 组件在小程序端渲染为页面。 目录 需求分析 页面布局 下载 Markdown 内容 使用 towxml 渲染 Markdown 代码与预览效果对比 1. 需求分析 在本次实战中,豆子碎片文章页面需要实现以下功能: 从服务器下载 Markdown 内容 使用 towxml 组件将 Markdown 内容渲染为页面 2. 页面布局 豆子碎片文章页面需要包含以下几个部分: 标题区域,用于展示文章标题 内容区域,用于展示渲染后的 Markdown 内容 示例代码 { "pages": ["pages/index/index", "pages/article/article"], "window": { "navigationBarBackgroundColor": "#ffffff", "navigationBarTextStyle": "black", "navigationBarTitleText": "豆子碎片文章" } } <view class="container"> <view class="article-title">{{title}}</view> <import src="/towxml/entry.wxml" /> <template is="entry" data="{{...articleContent}}" /> </view> .container { padding: 20px; } .article-title { font-size: 24px; font-weight: bold; margin-bottom: 20px; } 3. 下载 Markdown 内容 通过微信小程序的wx.request方法,从服务器下载 Markdown 内容。 ...

2026-01-05 · 2 min · Eagle

优化内容类小程序快速审核通过

我们已经知道如何上架,但是针对内容类小程序,我们还需要再特别注意一些内容。 引用真实案例!对于内容展示类的小程序,根据微信小程序的审核规则,个人主体的小程序不能涉及“资讯类”内容(如直接展示文章列表、新闻聚合等),否则会被要求以企业主体重新提交。我是技术类文章展示小程序,即使这样,也被拒多次,以下是针对这一问题的优化方案,既能规避审核风险,又能合理引导用户访问技术文章: 核心原则 内容不应牵涉政治、时事热点、色情暴力等信息,这个无需多说。 首页不直接展示文章内容列表,避免被判定为“资讯类”。这是资讯类默认布局,容易违反。 通过功能入口间接引导用户到二级页面(如搜索、分类、专题入口),在二级页面展示文章列表。 突出技术工具属性,而非内容聚合平台。一般指展示的内容没有相关性,不能突出主题。 内容应偏重于教程、经验性质,而非实时信息,不应具有时效性。比如相关技术最新消息很容易判定为资讯类。 在确定核心原则之后,以下几个推荐布局可以避免判定为资讯类布局。 方案 1:搜索 + 分类入口 + 专题推荐 <view class="container"> <!-- 搜索框(保持顶部) --> <view class="search-box"> <input placeholder="搜索技术文章" bindtap="navigateToSearch" /> </view> <!-- 快捷分类入口(工具按钮) --> <view class="category-buttons"> <view class="button" bindtap="navigateToCategory" data-category="前端">前端技术</view> <view class="button" bindtap="navigateToCategory" data-category="后端">后端架构</view> <view class="button" bindtap="navigateToCategory" data-category="数据库">数据库优化</view> </view> <!-- 专题推荐(以“技术专题”形式包装) --> <view class="special-topics"> <text class="section-title">精选技术专题</text> <view class="topic" bindtap="navigateToTopic" data-id="1"> <image src="/images/topic1.png" mode="aspectFill" /> <text class="topic-title">前端性能优化实战</text> </view> <view class="topic" bindtap="navigateToTopic" data-id="2"> <image src="/images/topic2.png" mode="aspectFill" /> <text class="topic-title">高并发系统设计指南</text> </view> </view> </view> 方案 2:搜索 + 问题解答入口 + 技术资源导航 <view class="container"> <!-- 搜索框 --> <view class="search-box"> <input placeholder="输入技术问题关键词" bindtap="navigateToSearch" /> </view> <!-- 技术问题快速入口(做成问答功能) --> <view class="qa-section"> <text class="section-title">常见技术问题</text> <view class="qa-item" bindtap="navigateToArticle" data-id="101"> <text>如何解决 Vue 3 响应式丢失问题?</text> </view> <view class="qa-item" bindtap="navigateToArticle" data-id="102"> <text>MySQL 索引优化最佳实践</text> </view> </view> <!-- 技术资源导航(强调工具属性) --> <view class="resource-nav"> <view class="nav-card" bindtap="navigateToPage" data-page="opensource"> <image src="/images/opensource.png" /> <text>开源项目推荐</text> </view> <view class="nav-card" bindtap="navigateToPage" data-page="tools"> <image src="/images/tools.png" /> <text>在线开发工具</text> </view> </view> </view> 关键审核规避策略 避免“文章列表”字眼: ...

2026-01-05 · 1 min · Eagle

在列表中搜索符合条件的内容

当我们创建笔记索引内容后,如何搜索是个关键的问题,目前的设计就是先搜索 name,看名称中是否包含关键字,然后查找 kws 数组,查看数组中是否有关键字,最后查看 tags 数组中是否有关键字?查到以后取出来,返回数组列表。 数据的结构如下: [{ "id":"id1", "name":"小星星", "kws":["爱","崇拜"], "tags":["想法","感情"], }] 如果使用 JS 来实现,那么代码如下: /** * 搜索笔记 * @param {Array} notes 笔记数组 * @param {String} keyword 搜索关键词 * @returns {Array} 匹配的笔记数组 */ function searchNotes(notes, keyword) { if (!keyword || typeof keyword !== 'string') { return []; // 如果关键词无效,返回空数组 } // 统一转换为小写以便不区分大小写搜索 const lowerKeyword = keyword.toLowerCase(); return notes.filter(note => { // 检查名称是否包含关键词 const nameMatch = note.name && note.name.toLowerCase().includes(lowerKeyword); // 检查kws数组是否包含关键词 const kwsMatch = Array.isArray(note.kws) && note.kws.some(kw => kw && kw.toLowerCase().includes(lowerKeyword)); // 检查tags数组是否包含关键词 const tagsMatch = Array.isArray(note.tags) && note.tags.some(tag => tag && tag.toLowerCase().includes(lowerKeyword)); // 如果任一条件匹配,则包含该笔记 return nameMatch || kwsMatch || tagsMatch; }); } 这里支持多维度搜索,同时搜索名称、关键词和标签三个字段,当搜索时,先将关键字转为小写,这样比较时不区分大小写。为了性能优化,使用 filter 和 some 方法,减少不必须要的循环。

2026-01-05 · 1 min · Eagle

在小程序文章页面动态添加广告

这是一项含金量非常高的技能,在页面中动态添加广告,可以增加广告曝光量和提高点击率。 小程序使用 Markdown 来编写文章内容,存储在服务器或者后端对象存储,小程序打开时,会从服务器拉取文章索引,然后展示文章列表页面给用户浏览。当用户点击列表上的某篇文章时,小程序下载该篇文章的 Markdown 内容,并在小程序端使用 towxml 组件进行解析渲染,该组件可以将 Markdown 内容渲染成 WXML 文件内容。 文章页面集成了流量主原生模板广告。起初是在页面顶端显示,直接在 article.wxml 页面顶部添加模板广告代码,就可以集成广告了,非常的简单。当前文章页面布局如下:从上到下广告组件、Markdown 文章内容、页面底部说明。但是当实际上线后,发现这样的广告曝光量虽然非常高,但是页面整体看起来非常的不舒服,不美观。然后将广告位置调整到文章底部,因为文章底部和页面底部之间有自定义内容,广告看起来还比较舒服。当前文章页面布局如下:从上到下 Markdown 文章内容、广告组件、页面底部说明。上线之后,发现实际的曝光量降低了很多,因为很多用户是不会浏览到页面底部的。所以广告也不会曝光。得想想办法。在写公众号时,发现公众号插入到文章中间的广告模式不错。我感觉这是一个不错的方案。可以这样试试。 现在就是如何将广告显示在文章中间。文章内容是使用 towxml 渲染的。比如,将广告显示在第一个段落之后。最初的想法,是查找文章页面的段落 p 元素,然后动态插入广告。根据微信官方文档内容,发现可以使用 wx.createSelectorQuery API,但当实际调试后,发现它无法穿透查询 towxml 生成的内容。然后使用 this.createSelectorQuery,这个是查询组件元素使用的,发现这个确实可以查询组件的元素,也确实查找到了 towxml 生成的内容的顶层元素,但是 towxml 生成的内容是多层嵌套,towxml 下面的组件是 decode,decode 然后再进行嵌套自定义组件。也就是说页面中存在着多个#shadow-root,它用来隔离元素和样式。感觉如果这样的布局使用这种方案行不通了。或者即使实现也非常的复杂,还是需要修改 towxml 组件。 当上面通过查询页面元素行不通后,就得想其它办法。虽然这个方案没有解决问题,但是学到了很多东西,以后在同一个#shadow-root 下的元素查找技能就学会了。本来一直不想动 towxml 库,现在的方案只有修改这个库了,来适配自己的需求。towxml 库的原理如下,解析原始的 Markdown 文件或者 Html 文件生成 AST,然后将 AST 转换成小程序 WXML 标签和结构。这里有两种方法:一种是在解析时处理,生成对应的广告组件;另一种是解析成对象后再插入数据。第一种方法实现后,就可以在 HTML 或 Markdown 文件中加入广告标签,然后 towxml 自动解析。虽然这一种功能看起来更强大,但是我的实际场景不是太适合,我的文章来源 Markdown 内容不一定是我编写,我的小程序只是内容展示平台。所以只能选择第二种方法,这个方法更灵活,可以在解析后的 AST 里插入广告数据,然后在 WXML 页面里根据条件进行渲染。这样用户提交的内容,我不进行修改就可以显示广告。想想还是不错的。 接下来就要考虑如何在 AST 插入数据,以及插入到哪个位置?如何查找段落位置?页面如何显示?我可以修改 towxml 库,以适配广告组件渲染,或者在自己的文章页面进行广告组件渲染。无论使用哪种方法,完整的数据流应该是这样的:Markdown 原始文件 -> Towxml 解析 -> 插入广告节点 -> 绑定广告数据 -> 渲染带广告的 WXML。剩下的内容就是想办法解决渲染广告组件这个问题了。 ...

2026-01-05 · 1 min · Eagle

在小程序页面查找元素

为了实现在页面中动态中插入广告,我们需要学会在小程序中查找元素的技能。例如,我要将广告显示在某某元素之后。 在微信小程序开发中,由于逻辑层(JavaScript)与视图层(WXML)分离的特性,开发者无法直接操作 DOM 元素。为了获取页面中某个元素的属性(如位置、尺寸等),小程序提供了 wx.createSelectorQuery API,通过创建节点查询对象来间接操作元素。 wx.createSelectorQuery 用于创建一个节点查询对象(SelectorQuery 实例),开发者可以通过该对象选择页面中的元素,并获取其布局信息(如宽高、位置等)。除了 wx.createSelectorQuery 外,还有一个 this.createSelectorQuery,他们之间的区别是一个用在页面中,一个用在组件组件中。 当我们创建了查询对象后,我们需要选择元素,选择元素有以下几种: select(selector):选择匹配选择器的第一个节点。 selectAll(selector):选择所有匹配的节点。 selectViewport():选择视口(可用于获取滚动容器的信息)。 当元素匹配到之后,我们可以获取元素属性,获取节点属性的方法有: boundingClientRect():获取节点的位置和尺寸。 scrollOffset():获取节点的滚动位置(适用于滚动容器)。 fields({…}):自定义需要获取的字段(如 id,dataset,rect 等)。 最后,一定要记得调用 exec(callback),这样结果才能查询出来。 下面我们举个小示例,可以方便地看到它如何使用?在这个示例中,我们将获取元素的尺寸和位置。场景如下:点击页面按钮,获取页面中某个 view 元素的宽度、高度和位置信息。 1,WXML 文件如下: <view class="target-box" id="target">我是目标元素</view> <button bindtap="getElementInfo">获取元素信息</button> 2,JS 文件如下: Page({ getElementInfo() { // 1. 创建查询实例 const query = wx.createSelectorQuery(); // 2. 选择元素并指定获取的属性 query.select('#target').boundingClientRect(); // 3. 执行查询 query.exec((res) => { // res 是包含查询结果的数组 const rect = res[0]; console.log('元素宽度:', rect.width); console.log('元素高度:', rect.height); console.log('元素位置:', rect.left, rect.top); }); } }); 如果代码没有错误的话,控制台将输出类似如下内容: ...

2026-01-05 · 1 min · Eagle

Hugo 进阶:集成 Google Analytics 4 与站点收录优化

完成网站搭建后,下一步便是集成 Google Analytics (GA4) 以及优化搜索引擎收录。对于我来说,这不仅仅是技术配置,更是将以前域名的冗余资源清理掉,赋予这个站点一个干净、纯粹的起点。 1. 集成 Google Analytics 统计分析 在 FixIt 主题中,集成统计分析需要配置 Hugo 的内置服务与主题插件。 在 hugo.toml 文件中进行如下配置: # 1. 开启 Hugo 内置的 Google Analytics 服务 [services] [services.googleAnalytics] ID = 'G-XXXXXXXXXX' # 替换为您在 GA4 获取的“衡量 ID” # 2. 配置 FixIt 主题的分析插件 [params] [params.analytics] enable = true type = "google" # 指定类型为 google [params.analytics.google] id = "G-XXXXXXXXXX" async = true # 开启异步加载,避免统计代码影响网页首屏加载速度 站长提示:配置完成后,建议在本地运行 hugo server 并使用 F12 检查网络请求,确认是否有来自 google-analytics.com 的数据上报。 2. 提交 Sitemap 与清理旧资源 为了让 Google 更好地爬取新内容,并彻底告别过去,我们需要通过 Google Search Console 进行以下操作。 ...

2026-01-02 · 1 min · Eagle

Hugo 进阶:FixIt 主题深度配置(目录结构、多级菜单与 Logo)

在完成第一篇文章的发布后,趁热打铁记录下 FixIt 主题的深度定制备忘,重点解决内容组织架构与导航体验。 1. 内容目录架构:从逻辑到物理组织 为了方便管理 9 年来的技术沉淀,我决定不依赖 Hugo 的扁平化展示,而是通过文件夹在物理层面进行分类,将其划分为 “项目实战” 与 “技术随笔” 两大板块。 项目实战 (Projects) 在 content/projects/ 目录下,采用“一项目一文件夹”的结构。每个文件夹内放置 _index.md 标识该板块为一个 Section。 路径示例:content/projects/mole-client/_index.md 配置内容: +++ title = "Mole 客户端 (frp管理)" layout = "section" draft = false +++ 注意:在父级 projects/ 下也要放置一个 _index.md 作为项目总列表。 技术随笔 (Posts) 日常记录存放于 content/posts/。不需要 _index.md,直接通过文章开头的 Front Matter 进行分类。 示例 Front Matter: categories: ["deploy"] # 将分类设为 deploy,Hugo 会自动将其归档至对应分类页 2. 导航菜单配置:构建多级下拉菜单 在 hugo.toml 中,通过 identifier 和 parent 的配合,我为“技术笔记”板块设计了下拉菜单,使导航更具条理性。 [menu] # 首页入口 [[menu.main]] identifier = "home" name = "首页" url = "/" pre = '<i class="fa-solid fa-home fa-fw"></i>' weight = 1 # --- 技术笔记下拉菜单 --- # 1. 父级入口:点击不跳转,仅用于展开子项 [[menu.main]] identifier = "notes" name = "技术笔记" url = "#" pre = '<i class="fa-solid fa-book fa-fw"></i>' weight = 2 # 2. 子菜单:小程序 [[menu.main]] parent = "notes" identifier = "mp" name = "小程序" url = "/categories/mp/" pre = '<i class="fa-solid fa-mobile-screen-button fa-fw"></i>' weight = 1 # 3. 子菜单:Go 语言 [[menu.main]] parent = "notes" identifier = "go" name = "Go 语言" url = "/categories/go/" pre = '<i class="fa-brands fa-golang fa-fw"></i>' weight = 2 # 4. 子菜单:Rust 语言 [[menu.main]] parent = "notes" identifier = "rust" name = "Rust 语言" url = "/categories/rust/" pre = '<i class="fa-brands fa-rust fa-fw"></i>' weight = 3 # 5. 子菜单:部署运维 [[menu.main]] parent = "notes" identifier = "deploy" name = "部署实战" url = "/categories/deploy/" pre = '<i class="fa-solid fa-cloud-arrow-up fa-fw"></i>' weight = 4 # --- 其他一级菜单 --- [[menu.main]] identifier = "projects" name = "项目实战" url = "/projects/" pre = '<i class="fa-solid fa-layer-group fa-fw"></i>' weight = 10 [[menu.main]] identifier = "about" name = "关于我" url = "/about/" pre = '<i class="fa-solid fa-user-circle fa-fw"></i>' weight = 20 3. 视觉识别:更换站点 Logo 作为“ 豆子技术站”的标志,我选择了 Ostrich 图标。 ...

2026-01-01 · 2 min · Eagle

从零到一:Hugo 项目部署 Cloudflare Pages 避坑指南

今天“ 豆子技术站”正式上线了!虽然部署过程只用了几个小时,但中间踩了不少坑。为了让大家少走弯路,我把这段折腾经历记录下来。 为什么选择 Hugo + GitHub + Cloudflare? 我有自己的服务器,最初想用 mdBook。但 mdBook 适合书籍展示,格式太固定,不适合折腾。最终我选择了 Hugo —— 虽有一段时间没用,但基础还在。 在主题选型上我花了不少时间,从花哨的 Blowfish 转向了更严谨的 FixIt。虽然我的重心正转向 Go 和 Rust,但 2026 年初的首要任务是搭建好这个支持中英双语的“根据地”。 站长忠告:切忌心急。 很多弯路其实是因为没读懂 Cloudflare 的英文提示就直接上手导致的。 避坑指南:我踩过的六个坑 1. 误判服务:Workers 还是 Pages? 这是最尴尬的一个坑。我一直在 Cloudflare Workers 里折腾,结果页面永远显示 Hello World。AI 告诉我是配置文件没生效,我折腾半天无果,最后通过 Google 搜索才发现:我要部署的是静态站(Pages),而不是函数脚本(Workers)。换成 Pages 服务后,一遍过。 2. DNS 托管的“心理障碍” Cloudflare 的免费计划要求将 DNS 托管给它。起初我很抗拒,因为我还有阿里云的子域名(如小程序后端 mp)在运行,担心影响原有业务。 解决方法:在域名注册商后台将 DNS 服务器换成 Cloudflare 提供的地址。 关键点:激活后,记得把原来的解析记录在 Cloudflare 重新添加一遍。以后新增解析都在 Cloudflare 完成。 3. 页面样式“裸奔”:baseURL 没填对 部署完成后,网站能打开但没有样式(CSS 加载失败)。 原因:F12 检查发现 URL 地址不对。 解决:在 hugo.toml 中将 baseURL 修改为你的自定义域名。 进阶:在 Cloudflare 构建命令中使用 hugo -b $CF_PAGES_URL,系统会自动处理域名。 4. CNAME 无法直接生效 我起初打算在原域名商处使用 CNAME,但死活打不开。事实证明,为了享受 Cloudflare 的全套防护和证书服务,DNS 托管是最高效的选择。 ...

2026-01-01 · 1 min · Eagle