把项目做成像 frp 那样的多平台 Release

把项目做成像 frp 那样的多平台 Release Mole 项目已经开源,打算把仓库在 GitHub Release 页面中发布各个平台的安装包/二进制(像 frp 那样),已经完成脚本,可参考.github/workflows/release.yml,这个用来记录实现过程与遇到的坑,便于自己回顾。 大致流程 查阅 GitHub Actions、goreleaser、actions/create-release、actions/upload-release-asset 等资料。 在仓库中写一个 matrix workflow,在 Windows / macOS / Ubuntu runner 上分别构建并打包。 修复构建中出现的依赖与环境差异问题。 将构建产物上传为 Release asset(先查找/创建 release,再上传各平台包)。 调试并保证上传在 Release 页面能看到各平台包。 今天遇到并解决的主要问题(按流程顺序) Ubuntu 图形库版本不一致 问题:教程写的是 libwebkit2gtk 4.0,但在新版 Ubuntu runner 上只能安装 4.1。 处理:在 CI 的 apt 安装里兼容 4.0 和 4.1(尝试 4.1,或用 || 逻辑兼容两种包名)。 wails3 安装地址写错导致安装失败 问题:go install 用错了模块路径,安装一直失败。 处理:修正为官方地址(例如 github.com/wailsapp/wails/v3/cmd/wails3@latest),并确保 GOPATH/bin 在 PATH 中。 wails3 的 -platform / -o 参数在我的环境不可用 ...

2026-01-07 · 2 min · Eagle

Markdown 语法教程

Markdown 语法教程 我们使用 Markdown 作为我们内容的格式,所以学习 Markdown 就显得很必要了。 Markdown 是一种轻量级标记语言,后缀为.md。因我常用 Markdown 来写一些内容,记录下来常用语法格式。方便自己查找使用,也增加自己的一些流量。Markdown 可以用来撰写电子书,文章,博客等。介绍标题、段落的使用。 标题 # 一级标题 ## 二级标题 ### 三级标题 #### 四级标题 ##### 五级标题 ###### 六级标题 段落 Markd 段落没有特殊的格式,直接编写文件就好,段落的换行使用两个以上空格加上回车。也可以使用一个空行来表示重新开始一个段落。 字体 使用如下格式分别表示斜体,粗体,粗斜体。 *斜体文本* _斜体文本_ **粗体文本** __粗体文本__ ***粗斜体文本*** ___粗斜体文本___ 分割线 你可以在一行中用三个以上的星号、减号、底线来建立一个分割线,行内不能有其它东西。你也可以在星号或减号中间插入空格。 *** * * * ***** - - - --------- 删除线 如果段落上的文字要添加删除线,只需要在文字的两端加上两个波浪线~~即可。 ~~删除线~~ 下划线 下划线可以通过 HTML 的标签来实现: <u>下划线文本</u> 脚注 脚注是对文本的补充说明。 [^要注明的文本] 列表 支持有序列表和无序列表。 无序列表使用星号、加号、减号作为列表标记,这些标记后面要添加一个空格,然后再填写内容。 * 第一项 * 第二项 * 第三项 + 第一项 + 第二项 + 第三项 - 第一项 - 第二项 - 第三项 有序列表使用数字并加上.号来表示。 ...

2026-01-05 · 2 min · Eagle

Visit 项目维护Protobuf文件

Visit 项目维护 Protobuf 文件 豆子碎片小程序在版本 5.0 开始使用 Protobuf 传输和存储数据。在使用 Protobuf 前,使用的是 JSON 数据,原始 JSON 数据文件大小为 10KB,在生成 PB 文件后,PB 文件只有 4KB。本文章主要记录如何维护 Protobuf 数据,和开发中碰到的问题记录。 现在由于实际需求和维护原因,又切回使用 JSON 文件。 目前是使用 JSON 文件维护原数据,后期可以考虑使用数据库来存储。将 JSON 文件转为 PB 文件的工具是使用 Python 写的脚本。 下面开始一步一步介绍,直到 PB 文件可以使用。 一、首先定义 data.proto 文件,这个文件 Python 和 JavaScript 都需要用到。 syntax = "proto3"; message DataItem { string aid = 1; string name = 2; string category = 3; string label = 4; int32 grade = 5; } message DataList { repeated DataItem items = 1; } 上面的内容就是描述了一个数组对象。 ...

2026-01-05 · 3 min · Eagle

笔记列表页面布局

笔记列表页面布局 这是现在小程序中实际用到的列表页面,请看真实的笔记列表页面需求: 页面包含三部分,广告、内容介绍、笔记列表。其中,广告占页面高度 260rpx,内容介绍高度 200rpx,笔记列表是一个卡片列表,卡片内容分为上下两部分,上部分是标题,下部分是标签,标签可以有多个,一行内显示,两个标签之间要留有间隙。当列表内容很多,一个屏幕无法显示出来时,要可以滑动查看列表内容。滑动时,上方的广告不滑动,内容介绍和笔记列表进行滑动,显示最新内容。 主题样式为 WEUI,但是标签为蓝色。 广告为 Banner 广告,高度为 120px,如果转换为 rpx 大概为 240rpx。所以广告上下可以留一些间隙。 内容介绍是卡片样式,分为上下两部分,上方为项目标题,介绍这个笔记列表归属哪个项目,下方是内容介绍,介绍这个项目是什么。 笔记列表的内容,为该项目从想法到实现的全部内容,每个文件都是一段内容,介绍想法、布局、实现、部署等等。以 Markdown 文件为载体,在列表页面只显示文件的标题,和人为绑定的标签,标签可以为多个,例如想法、开发、部署等,当为开发时,可以牵涉到使用什么语言,例如 Go、Js 等。 当我们了解了需求之后,可以发现这个通过 flex 布局可以很好的实现。 其中页面 WXML 如下: <view class="container"> <!-- 广告部分 - 固定不滚动 --> <view class="ad-container"> <ad unit-id="your-ad-unit-id" class="banner-ad"></ad> </view> <!-- 可滚动区域 --> <scroll-view class="scroll-area" scroll-y> <!-- 内容介绍部分 --> <view class="content-intro"> <view class="intro-title">{{projectTitle}}</view> <view class="intro-description">{{projectDescription}}</view> </view> <!-- 笔记列表 --> <view class="note-list"> <block wx:for="{{notes}}" wx:key="id"> <view class="note-card" bindtap="onNoteTap" data-id="{{item.id}}"> <view class="note-title">{{item.title}}</view> <view class="note-tags"> <block wx:for="{{item.tags}}" wx:key="*this"> <view class="tag">{{item}}</view> </block> </view> </view> </block> </view> </scroll-view> </view> 样式 WXSS 如下: ...

2026-01-05 · 2 min · Eagle

变现路径:广告接入与付费会员设计

变现路径:广告接入与付费会员设计 当开发完小程序后,如何收益成为一件重要的事情。 在微信小程序中,变现路径的选择对开发者的收益有着重要影响。本文将对比流量主广告、自定义付费墙等方案的收益模型,帮助开发者选择合适的变现途径。 目录 流量主广告 自定义付费墙 收益模型对比 选择适合的变现方案 1. 流量主广告 什么是流量主广告? 流量主广告是微信提供的一种变现方式,开发者可以在小程序中接入微信广告组件,通过展示广告获取收益。常见的广告形式包括激励视频广告、插屏广告、Banner 广告等。 流量主广告的优势 易于接入:微信提供了完善的广告组件和接口,开发者只需简单配置即可接入广告。 收益稳定:广告收益与用户的广告点击和观看次数相关,流量大的小程序可以获得稳定的广告收入。 不影响用户体验:合理布局广告位置,不会对用户体验造成过多干扰。 流量主广告的劣势 广告依赖性强:收益依赖于广告的点击率和展示量,受广告市场波动影响较大。 用户反感:广告数量过多或位置不合理,可能会引起用户反感,影响用户留存率。 示例代码 Page({ data: { adUnitId: "YOUR_AD_UNIT_ID", }, onLoad: function () { // 初始化广告组件 const videoAd = wx.createRewardedVideoAd({ adUnitId: this.data.adUnitId }); videoAd.onLoad(() => {}); videoAd.onError((err) => {}); videoAd.onClose((res) => { if (res.isEnded) { // 用户完整观看广告,可以给予奖励 } else { // 用户提前关闭广告,不给予奖励 } }); this.videoAd = videoAd; }, showAd: function () { this.videoAd.show().catch(() => { // 广告播放失败 }); }, }); 2. 自定义付费墙 什么是自定义付费墙? 自定义付费墙是一种内容付费模式,用户需要支付一定费用才能访问小程序中的特定内容或功能。开发者可以根据需求设计不同的付费策略,如单次付费、订阅付费等。 ...

2026-01-05 · 1 min · Eagle

遍历文件夹更新JSON文件内容

遍历文件夹更新 JSON 文件内容 最近,有一个需求,需要从根目录下查找子文件夹,如果子文件夹下的文件是.md 后缀,并且在原始的 data.json 数据中存在,则取出来生成新的对象。然后存入到 data-temp.json 文件中。 使用 Python 实现的,真的很方便。如果自己复制粘贴,需要好长时间,但是使用脚本一秒就够了。 import os import json import re from typing import List, Dict # 固定路径配置 SCAN_DIR = "扫描目录" JSON_FILE = "原始JSON数据文件" OUTPUT_FILE = "存入JSON数据文件" def is_valid_folder(folder_name: str) -> bool: """检查文件夹名是否不包含数字""" return not bool(re.search(r'\d', folder_name)) def find_matching_files() -> List[Dict]: """ 查询指定目录下的子文件夹(排除含数字的),查找.md文件,并从JSON文件中查找匹配的记录 """ # 读取并预先加载JSON文件 try: print(f"正在加载JSON文件: {JSON_FILE}") with open(JSON_FILE, 'r', encoding='utf-8') as f: json_data = json.load(f) # 创建以id为键的字典提高查询效率 id_map = {str(item.get('id')): item for item in json_data if isinstance(item, dict) and 'id' in item} print(f"成功加载 {len(id_map)} 条JSON记录") except FileNotFoundError: print(f"错误:JSON文件 {JSON_FILE} 未找到") return [] except json.JSONDecodeError: print(f"错误:JSON文件 {JSON_FILE} 格式不正确") return [] except Exception as e: print(f"加载JSON数据时出错: {e}") return [] results = [] valid_folders = [] print(f"\n开始扫描目录: {SCAN_DIR}") # 遍历根目录下的所有子文件夹 for category in os.listdir(SCAN_DIR): # 跳过包含数字的文件夹 if not is_valid_folder(category): continue subdir_path = os.path.join(SCAN_DIR, category) # 确保是目录 if not os.path.isdir(subdir_path): continue valid_folders.append(category) print(f"发现有效分类目录: {category}") # 遍历子目录中的.md文件 for filename in os.listdir(subdir_path): # 只处理.md文件 if not filename.endswith('.md'): continue # 使用完整的文件名(保留.md后缀)作为ID file_id = filename # 在预先加载的JSON数据中查找匹配的ID if file_id in id_map: item = id_map[file_id] # 创建记录 record = { 'id': item.get('id'), 'name': item.get('name', ''), 'category': category, 'kw': '', 'plat': 'mp', 'filename': filename } results.append(record) print(f"匹配成功: {category}/{filename} -> ID {item.get('id')}") # 打印扫描摘要 print("\n扫描完成,结果摘要:") print(f" - 有效分类目录: {len(valid_folders)} 个") print(f" - 匹配到的文件: {len(results)} 个") return results def save_to_json(data: List[Dict]) -> bool: """ 将数据保存为JSON文件 """ try: with open(OUTPUT_FILE, 'w', encoding='utf-8') as f: json.dump(data, f, ensure_ascii=False, indent=2) print(f"\n结果已保存到: {OUTPUT_FILE}") return True except Exception as e: print(f"\n保存文件时出错: {e}") return False if __name__ == "__main__": # 获取匹配结果 matched_records = find_matching_files() # 保存结果 if save_to_json(matched_records): print("操作成功完成") else: print("操作完成,但保存结果时出现问题") 可以以此为模板,碰到合适场景可以直接使用。

2026-01-05 · 2 min · Eagle

处理Excel文件获取IP网段

处理 Excel 文件获取 IP 网段 我获取了一份有 IP 网段的 Excel 表,这些 IP 网络段是用来在登录后台时判断 IP 是否允许时使用。 Excel 表的格式如下: 起始号段 结束号段 1.192.0.0 1.192.7.255 1.192.16.0 1.192.71.255 我现在需要处理这个 Excel,将其中的 IP 地址转换为数字形式,并生成适合导入 MySQL 的 CSV 格式数据。 现在是使用 Python 进行处理。需要安装 pandas,使用 pip 安装pip install pandas。IP 地址转换为整数时使用的是无符号 32 位整数表示。 脚本代码如下: import pandas as pd import ipaddress # 1. 读取Excel文件(请将'ip_ranges.xlsx'替换为您的实际文件名) excel_file = 'ip_ranges.xlsx' df = pd.read_excel(excel_file) # 2. 定义IP地址转数字的函数 def ip_to_int(ip_str): """将IP地址字符串转换为整数""" return int(ipaddress.ip_address(ip_str)) # 3. 转换IP地址为数字 df['起始数字'] = df['起始号段'].apply(ip_to_int) df['结束数字'] = df['结束号段'].apply(ip_to_int) # 4. 生成输出结果 output_lines = [] for _, row in df.iterrows(): line = f"{row['起始数字']},{row['结束数字']}" output_lines.append(line) # 5. 打印转换结果 print("IP地址范围转换为数字后的结果:") for line in output_lines: print(line) # 6. 保存为CSV文件(可直接导入MySQL) output_csv = 'ip_ranges_converted.csv' df[['起始数字', '结束数字']].to_csv(output_csv, index=False, header=False) print(f"\n转换结果已保存到 {output_csv} 文件,可直接导入MySQL。") 将这段代码保存为一个.py 文件,例如 ip_converter.py。然后将 Excel 文件和脚本保存在同一个目录,然后运行脚本即可。

2026-01-05 · 1 min · Eagle

从按钮开始:组件的属性与事件绑定

从按钮开始:组件的属性与事件绑定 我们通过按钮来举例,它就是一个组件,在微信小程序开发中,组件是构建用户界面的基本单元。本文将以 button 组件为例,介绍组件的属性与事件绑定,并通过一个点击弹窗的示例对比代码与预览效果。 目录 组件的基本属性 事件绑定 实现点击弹窗 代码与预览效果对比 组件的基本属性 微信小程序的 button 组件有多个属性,如 type、size、plain、disabled 等,用于控制按钮的样式和行为。 示例代码 <!-- button.wxml --> <view class="container"> <!-- 默认按钮 --> <button>默认按钮</button> <!-- 主按钮 --> <button type="primary">主按钮</button> <!-- 警告按钮 --> <button type="warn">警告按钮</button> <!-- 禁用按钮 --> <button disabled>禁用按钮</button> </view> 事件绑定 微信小程序的组件可以通过 bind 或 catch 前缀绑定事件处理函数。常见的事件有点击事件、长按事件等。 示例代码 <!-- button.wxml --> <view class="container"> <button bindtap="handleTap">点击我</button> </view> // button.js Page({ handleTap: function () { wx.showToast({ title: "按钮被点击", icon: "success", duration: 2000, }); }, }); 实现点击弹窗 接下来,我们通过一个完整的示例实现点击按钮弹出弹窗,并展示代码与预览效果对比。 ...

2026-01-05 · 2 min · Eagle

豆子碎片的故事

豆子碎片的故事 豆子碎片小程序很早就注册了,刚开始做的功能就是浏览 Go Web 框架 Beego 的教程。当时,正在自学 Beego,而 Beego 的网站时常打不开,就想做一个小程序,可以浏览 Beego 内容。当时看到了 towxml 组件,正好可以实现 Markdown 转 Wxml,于是就将 Beego 的 内容文档写成 Markdown 格式,做成了小程序。小程序的项目名叫 visit,就是浏览的意思。 当时小程序还是使用的云环境,云环境免费,并提供 dev 和 pro 两个环境。将 Beego 的 Markdown 文件存在云存储中。使用云函数调用数据。在小程序端使用 towxml 解析 Markdown 数据。当时发布小程序后,还是很受欢迎的。 中间歇了一段时间,没有更新文章,也没有维护小程序,小程序日浏览人数就下降了。云环境收费后,就彻底不管了。 后来,买了一台云服务器,为了练习自己所学的 Golang 语言,就将小程序重新拾起来。新做的项目内容是浏览唐诗,使用 Gin 框架进行后端开发,小程序端的布局和现在的豆子碎片布局差不多,只有一个首页,首页包含标题、一个搜索框、几个快捷按钮。项目很快完成,将小学和初中的唐诗、宋词、古诗经都录入了数据库,维护了很长一段时间。小程序上线后,每天也有几个人访问。在录入高中的唐诗内容时,就无法坚持下去了,高中的唐诗等内容都比较长,页面排版非常不美,所以高中的内容就没有录入。小程序在展示唐诗内容时,刚开始使用 Markdown 展示。后来想添加拼音,就将 Markdown 去掉,手撸 wxml,最后,拼音也添加了,但是费了很大劲,自己不擅长前端,美工。本还计划添加录音,但实在没有素材,真正做起来,费时费力,没有精力和动力就搁置了。 2024 年微信平台要求小程序都要备案。我有三个小程序,本想都放弃了,但后来,在工作中要用到音频格式转换,在小程序端实现操作起来是真的很方便,省时省力,不需要开电脑,也可以在任何时间操作。所以计划留下一两个吧。在备案的时候,注销了一个,剩下了两个,目前还在使用和维护,一个是豆子工具,用来制作工具,平时我也在用,一个是豆子碎片,记录技术经验和代码片段。在备案豆子碎片小程序时,小程序原名称叫找唐诗,提交备案后,微信备案客服人员说唐诗这两字不让用,让换个名称。在换名称的时候,我就在想这个小程序要不要放弃?不放弃做什么内容?已经有一个豆子工具小程序做工具了,没有必要再做一个工具类小程序吧。一直没有想好,等微信备案人员二次打电话时(真的很负责),才确定下来,做笔记类应用,记录日常开发经验和技巧以及代码片段,本想叫代码碎片,为了和豆子工具保持一致,小程序名称就叫豆子碎片,寓意像玩积木一样,学了本小程序内容,你也可以制作一款你想要的小程序。 豆子碎片项目已经开源,项目地址:https://github.com/littletow/visit 豆子碎片目前没有实名认证,如果你喜欢就收藏一下。豆子碎片面对的人群是有技术门槛的,后续看情况再进行实名认证。 豆子碎片的文章也已经在我公众号发布。有兴趣可以查看一下,一个程序员之小程序的成长与蜕变 想要查看豆子碎片,可以扫下面的小程序码:

2026-01-05 · 1 min · Eagle

豆子碎片内部逻辑

豆子碎片内部逻辑 虽然项目已经完成,但是温故而知新。我们梳理一下。结合以前学到的基础知识。我们进行总结。 APP 启动操作流程如下: APP 启动,加载设备信息,若没有则进行获取并缓存到系统,加载最新版本检查时间,若没有则下载版本文件,下载数据文件,然后缓存版本检查时间;若有,则和今天零时对比,若大于零时时间,则表示已经更新,从缓存中直接加载数据,若小于零时时间,则表示还未更新,从服务器下载版本文件,然后和本地的版本进行对比。如线上更新,则更新数据文件,并同步本地版本。然后将数据保存在文件系统中。如果没有更新,直接从文件系统中获取数据,然后存储到 app 的 artList 对象中。 界面渲染完毕后,会显示首页,首页和用户交互的有搜索框和分类按钮。 当使用搜索框搜索时,根据填写的内容作为关键字在 artList 列表中进行查询,查询步骤顺序,先查询标题,如果标题包含直接返回,然后查询关键词,如果包含直接返回,最后查询标签,如果包含直接返回。最后组成列表并进行分页,返回数据给页面。页面将跳转到列表页面,并渲染列表。列表中的每一项都可以点击,点击之后就进入到文章页。 文章页的功能将文章 Markdown 数据下载到本地,然后使用 towxml 插件将数据渲染并显示。 分类按钮是按照项目来分类,当点击按钮时,从文章索引列表中将相关的项目分类数据提取组成列表,分页之后返回给页面。

2026-01-05 · 1 min · Eagle