Mosquitto 安全加固从匿名访问到多用户 ACL 细粒度权限控制

我在豆子碎片项目中开始使用MQTT,比如文章上传后我使用了MQTT来通知审核的消息。这样就要求不能让张三接收到属于李四的消息。当然还有其它场景使用MQTT,比如ESP设备连接MQTT。比如用户上传文章后,我的ESP设备的LED就会闪烁,蜂鸣器也会响。我服务器上部署的MQTT Broker是mosquitto。在 Mosquitto 实例上配置身份验证非常重要,这样未经授权的客户端就无法连接。在 Mosquitto 2.0 及更高版本中,你必须明确选择身份验证选项,然后客户端才能连接。早期版本中,默认设置是允许客户端无需身份验证即可连接。 Mosquitto身份验证有三种选择:密码文件、身份验证插件和匿名访问。可以单独使用,也可以使用三个选项的组合。在 Mosquitto2.0 及更高版本中,可以通过配置文件中将 per_listener_settings 设置为 true,让不同的侦听器使用不同的身份验证方法。除了身份验证,还应该考虑访问权限控制,以确定哪些客户端可以访问哪些主题。我们来看下不同选项的配置。 密码文件设置 密码文件是一种将用户名和密码存储在单个文件中的简单机制。适合于有相对较少的静态用户。请注意,修改了密码文件后,需要重新加载 mosquitto。 创建密码文件,可以使用mosquitto_passwd工具,命令如下: mosquitto_passwd -c <password file> <username> 请注意,使用-c 标志将覆盖已存在的文件,如果向已存在文件添加,请去掉-c 标志。 要开始使用密码文件,需要配置 broker,在配置文件中,添加如下项: password_file <path to the configuration file> 密码文件必须能够被运行 mosquitto 的用户读取,也就是必须具备读取它的权限。 身份验证插件设置 如果需要更多的控制来认证用户,则需要使用认证插件。具体的特性依赖于使用的认证插件。 可使用的插件有: mosquitto-go-auth ,它提供了各种后端来存储用户数据,例如 mysql,postgresql,jwt 或 redis 等。 Dynamic security,动态安全插件,仅适用于 2.0 及更高版本,可提供原创管理的灵活的代理客户端、组和角色。 配置身份验证插件依赖你的 Mosquitto 的版本。版本 1.6.x 和以前的版本,使用auth_plugin选项。这个选项在版本 2.0 也被支持。 listener 1883 auth_plugin <path to plugin> 版本 2.0 及更高,使用plugin选项。 listener 1883 plugin <path to plugin> 我使用了这个选项,因为这个可以对接数据库,我可以方便的在数据库中维护权限,这样我不需要像密码文件那样,修改一个权限还需要重新启动Mosquitto。这对于对接其他系统或扩展业务非常方便。 匿名访问设置 配置匿名访问,请使用allow_anonymous选项。 ...

2025-01-01 · 1 min · Eagle

文章上传命令行客户端升级版本使用Cobra支持更多命令

对于添加更多的命令,使用 flag,就有点麻烦了,这次我们使用一个更高级的库 cobra。同时,我们使用 viper 替换 ini 库,这个库可以读取多种格式的配置文件,可以读取环境变量。 要实现的功能如下: 上传文章 文章删除 更新文章标题 更新文章关键字 更新文章内容 文章公开开关 文章加锁开关 根据标题查找文章 根据关键字查询文章 现在使用 cobra 实现上面的命令。 首先,我想要的效果如下: 上传文章 gart upload title keyword filename ispub islock 删除文章 gart remove uuid 更新文章标题 gart updatetitle uuid title 更新文章关键字 gart updatekeyword uuid keyword 更新文章内容 gart updatecontent uuid filename 更新文章公开或不公开 gart updatepub uuid ispub 更新文章加锁或不加锁 gart updatelock uuid islock 根据标题或关键字查找文章 gart search content 上面的 upload,remove,updatetitle,updatekeyword 等,在 cobra 中都是命令。title keyword 等都是参数。 我在读 cobra 文档后,最疑惑的地方就是标志和参数,这两者最大的区别就是标志需要在命令中添加–flag 然后是值,而参数是直接跟在命令后,直接就是内容的。标志可以更改程序的行为。 ...

2024-09-22 · 5 min · Eagle

使用Go自制豆子碎片文章上传命令行客户端

这是 Golang 实现的上传文章以及管理文章的一个命令行工具。 项目地址 https://gitee.com/littletow/upart-go 实现一个上传文章的命令行工具 这是一个最初的版本,使用 flag 和 ini 来实现文章上传功能。使用 flag 来解析命令行参数,使用 Ini 配置文件,记录识别码,以及 token。记录 token 的原因是因为每次启动命令行,都需要重新获取 token,为了减少 token 获取次数,在获取到 token 后,同时存储到 Ini 配置文件中。每次命令行启动,优先查看配置文件中的 token。 开发这个工具主要有以下几个技术要点: 从命令行获取参数 从配置文件中读取参数 读取文件内容 请求后端接口 我们通过分析后,需要定义以下几个参数: title 标题 keyword 关键字 filename MD 文件名 ispub 是否公开 islock 是否加锁 使用 Go 代码定义参数和结构体 var ( title string keyword string filename string ispub int // 1 pub islock int // 1 lock ) func init() { flag.StringVar(&title, "b", "", "文章题目") flag.StringVar(&keyword, "k", "", "文章关键字") flag.StringVar(&filename, "f", "", "MD文件") flag.IntVar(&islock, "l", 0, "是否加锁") flag.IntVar(&ispub, "p", 0, "是否开放") } func main(){ flag.Parse() } init() 在 main 函数前调用。需要在 main 函数中调用 flag.Parse(),这一步非常关键。之后就可以使用变量了。 ...

2024-09-21 · 3 min · Eagle

实战笔记:实现一个语音验证码自助拨打版本

自助语音验证码是通过拨打VOIP电话自动获取验证码,验证码可用来豆子笔记网站认证。这个纯粹是自己的兴趣驱动的。掌握了此项技术,你也可以应用于其它地方。 我是使用Asterisk实现的,Asterisk 是一个流行的开源通信平台,它提供了构建各种通信应用的灵活而强大的解决方案。它被广泛应用于企业内部电话系统、呼叫中心、语音邮件等场景。Asterisk 支持多种通信协议,包括 SIP、H.323、MGCP 和 SCCP,并且能够与传统的 PSTN 线路深度兼容。此外,Asterisk 还支持通过 Inter-Asterisk eXchange (IAX)协议进行语音 IP 传输,允许数据和语音同时在网络上传输。 自助语音验证码使用说明 你需要准备: 软电话,或者支持 VOIP 的电话机 AST 账户,AST 账户从豆子工具小程序中获取,功能菜单为获取 AST 账户。 注意:该功能目前已经下架,这里仅作为备忘记录。 首先,使用获取到的账户配置你的电话。然后拨打 8000,即可自动获取验证码。如果您有什么问题,欢迎关注公众号【技术源泉】私信我。 技术实现 它的工作原理是:用户通过 VOIP 电话连接到 Asterisk,当拨打固定的数字 8000时,Asterisk 将调用 AGI 接口获取服务端的API生成验证码,然后将获取到的验证码拆分通过语音文件播报给用户,播报完毕后自动挂断。 我们根据自助语音验证码原理,将自助验证码实现分解为以下几个技术要点,只要我们解决以下技术要点,就可以实现自助语音验证码。 技术要点: 1,如何播放音频文件?音频文件从哪里来?如何播放动态数据? 2,如何存储用户信息?如何使用数据库存储用户信息? 3,如何获取验证码? 下面内容是我对各个技术点的对应解决方案。 1,Asterisk 自带 Playback 应用,可以通过它播放音频文件。音频文件需要我们提前录制好,并且转换为对应的音频格式,最简单的方法就是使用手机上的录音机。当在拨号计划中多次执行 Playback 应用,Asterisk 会将音频流自动连接起来。所以我们可以使用循环多次执行 Playback 应用即可。 2,Asterisk 使用配置文件写入 SIP 用户信息,但当写入新的 SIP 用户信息后,需要重新加载配置文件。为了方便和第三方对接,我们推荐使用数据库。Asterisk 支持数据库,并且可以实时获取用户信息。 3,Asterisk 支持 AGI 接口,我们可以使用 AGI 获取第三方应用的验证码,获取后和提前录制好的文件结合起来进行播放。 当掌握了这些技术点后,我们就可以灵活应用到其它解决方案。 下面我们来看具体实现: 1,音频文件处理。我们使用手机上的录音机来录制音频文件。Android 录音机录制的音频文件格式为 mp3,如果是 amr 格式,请使用豆子工具音频格式转换功能,转成 mp3 格式文件。IOS 录音机录制的音频文件格式为 m4a,请使用豆子工具音频格式转换功能,转成 mp3 格式文件。我们还需要使用 ffmpeg 将 mp3 文件转成 g711a 格式文件。这个 mp3 转 g711a 功能也可以在豆子工具中使用。 ...

2022-08-19 · 2 min · Eagle

实战笔记:当我想查端口却没装 Telnet 时,我决定自己写个工具

作为开发者,你一定遇到过这种“抓狂”的时刻: 某次我急需确认服务器上的一个端口是否正常开启,习惯性地打开 Windows 命令行准备敲下 telnet 命令。结果弹出的却是:“telnet 不是内部或外部命令”。 由于系统刚重装,这个组件还没勾选。当我从控制面板找到它、安装、甚至可能还要重启系统时,这套复杂的操作让我陷入了反思:为了检测一个端口,至于这么麻烦吗? 更进一步想,如果我手头没有电脑,只有一部手机,我该如何快速判断服务器防火墙是不是忘了开? 痛点:环境依赖与生态限制 在实现这个功能之前,我有过两个思考维度: 环境依赖:无论是 Windows 的 Telnet 还是 Linux 的 nc,都依赖当前操作系统的环境配置。 小程序限制:微信小程序虽然有网络请求能力,但它必须配置服务器域名白名单。如果你想直接用小程序前端去探测一个随机的 IP 或端口,微信的底层安全机制是不允许的。 方案:Go 后端代劳,小程序只做“遥控器” 为了绕过这些限制,我在“豆子工具”里实现了一个远程端口检测功能。它的逻辑非常直接且高效: 前端交互:用户只需在小程序里输入目标服务器的 IP/域名 和 端口号。 后端核心:数据提交到我用 Go 编写的后端服务。 模拟连接:后端服务代替用户执行 TCP 连接探测。Go 语言的 net.DialTimeout 在这里非常实用,可以精准控制探测时间,避免由于网络超时导致的页面死等。 结果反馈:后端将“连接成功”或“连接失败”的结果返回给小程序展示。 价值:随时随地的“运维眼” 自从这个功能上线后,我解决了很多尴尬的场景: 排查防火墙:在云厂商后台改了安全组策略后,掏出手机点一下,秒知是否生效。 现场交付:在客户现场没有电脑时,快速确认后端服务是否已经拉起。 零部署成本:再也不用担心当前电脑有没有装 Telnet 或 Netcat。 技术实现 这里的难点在于服务器端实现,小程序端仅仅提交FORM表单就可以了。下面我们看看服务端的Go的核心实现: // 检测端口是否被打开? func IsPortOpened(ip string, port int) bool { addr := net.JoinHostPort(ip, fmt.Sprintf("%d", port)) conn, err := net.DialTimeout("tcp", addr, time.Second*3) if err != nil { return false } if conn != nil { conn.Close() return true } return false } 这里仅能探测TCP端口,UDP端口我还没有思路,等有思路了我再在这篇文章里补充。 ...

2018-02-05 · 1 min · Eagle

实战笔记:为了管好那堆记不住的密码,我给自己写了个全加密密码本

虽然现在的 App 普遍支持手机号或微信一键登录,但对于开发者来说,GitHub、服务器、各类海外服务的登录依然离不开“邮箱+密码”的传统模式。 我曾经历过一段漫长的“密码管理进化史”: Excel 时代:最早用一个加密的 Excel 表格记录,在电脑端还凑合。 Flutter App 时代:为了手机查看方便,我曾写过一个简单的 Flutter App,支持指纹查看,但功能简陋,只有新增功能,连“修改”功能都没有。 直到有一次,GitHub 要求我重新登录,由于我频繁重置密码且新旧密码不能重复,导致我彻底记混了。在另一台电脑前尝试了无数次失败后,我意识到:我需要一个足够安全、随时可用、功能完整的个人密码管理工具。 于是,“豆子工具”里的小程序版密码本诞生了。 1. 设计核心:绝对的隐私与自由 在设计之初,我就定下了两个原则:不联网、重加密。 纯离线运行:为了打消用户(包括我自己)对隐私的顾虑,我砍掉了所有联网备份功能。所有的密码数据仅存储在手机本地,备份只能通过聊天文件导出。 三重加密体系:这是我花费心血最多的地方。主密码(Master Password)不存储在设备上。 第一步:用主密码解密 RSA 私钥。 第二步:用私钥解密 AES 密钥。 第三步:用 AES 密钥解密具体的 JSON 序列化记录。 这种混合加密机制确保了即使手机丢失,只要主密码不泄露,数据依然是安全的。 2. 攻克开发中的“大山” 这个功能的开发过程远比我想象中痛苦。尤其是在小程序环境下处理文件缓存、二进制流转换和加解密逻辑,经常一个 Bug 就要调试好几天。 中途我好几次想过放弃,觉得用 Excel 也可以凑合。但每次想到反复重置密码的痛苦,还是咬牙坚持了下来。为了稳定性,我将原本不稳定的二进制存储改为了 UTF-8 编码的 JSON 序列化,最终实现了丝滑的操作体验,这都是心血的教训。 3. 功能完善:不仅仅是“存一下” 相比之前的 Flutter 版本,这次我补全了所有短板: 增删改查:支持搜索功能,输入标题就能快速定位账号。 备份恢复:加入了完整的备份机制,更换手机时可以轻松迁移数据。 批量操作:支持导入和导出,方便从其他平台平替过来。 密码提示:为了防范“忘记主密码”这个终极灾难,我加入了密码提示功能(建议设一个只有自己懂的暗语)。 4. 字段灵活,满足多样需求 每一条记录都包含了:标题、URL、用户名、密码、备注。 无论是一个服务器的 SSH 密码,还是一个冷门网站的登录信息,都能井井有条地分类存放。 5. 核心代码展示 纯小程序本地原生实现,首先是密码本初始化,它会根据用户输入的主密码,生成密码本文件。 // 第一次使用设置方法 async setupFirstTime(masterPassword, passwordHint) { try { // 1. 检查是否已有密码本 if (!passwordManager.hasPasswordBook()) { console.log('1. 创建空密码本...'); const createResult = await passwordManager.createEmptyPasswordBook(masterPassword, passwordHint); if (createResult.success) { console.log('✓', createResult.message); } else { console.log('创建失败:', createResult.error); wx.showToast({ title: createResult.error, icon: 'none' }); return; } } } catch (err) { console.log('创建密码本错误,', err) } // 保存主密码安装和提示 wx.setStorageSync('pwbookInstall', new Date().toLocaleString()); wx.setStorageSync('passwordHint', passwordHint || ''); wx.setStorageSync('pwbookBakTime', getSecTs()); this.setData({ isFirstTime: false, isUnlocked: true, showPasswordDialog: false }); wx.showToast({ title: '初始化成功', icon: 'success' }); this.loadPasswordList(); }, 创建密码本文件核心代码: ...

2018-01-11 · 3 min · Eagle

实战笔记:为了不再漏掉任何一个域名到期提醒,我做了个自动化检测工具

记录如何通过 Go 语言实现多域名 SSL 证书到期自动巡检,并通过钉钉/企业微信机器人实现精准预警。 在运维工作中,有一类事故极其低级却又杀伤力巨大:SSL 证书过期。 痛点:被动挨打的“救火”模式 由于业务需要,我手里管理着大量客户的域名。每个客户购买证书的渠道各异(阿里云、腾讯云或其他厂商),证书下来后通过微信或邮件发给运维,再手动配置到服务器上。 这套流程在客户少的时候还算正常。但随着客户增多,问题出现了:证书到期时间不一,全靠人工记忆。 总会有那么一两次,因为忙碌或者交接疏忽,某个域名证书悄悄过期了。直到客户反馈 APP 无法访问、浏览器弹出红色的安全告警,我们才急忙去“救火”。这种被动的局面不仅影响专业度,也给客户带来了实际损失。 方案:主动出击的自动化巡检 为了彻底根治这个“心病”,我决定做一个自动化的域名证书检测工具。 我的设想很简单:变“人找信息”为“信息找人”。 1. 实现原理 配置简单化:将所有需要监测的域名汇总成一个 txt 文件,每行一个域名,管理起来极其方便。 核心引擎(Go):使用 Go 语言开发后端服务,利用 cron 库开启定时任务,设定每天固定时间(如凌晨 4 点)执行一次巡检。 检测逻辑:程序自动循环读取域名列表,通过 TLS 握手获取证书的有效载荷,计算当前的剩余天数。 精准预警:我设定了一个“7天阈值”。一旦发现有域名将在 7 天内过期,程序会立即将这些域名汇总,并通过企微或者钉钉送达。 2. 消息触达:为什么选择机器人? 在小程序中,邮箱属于敏感隐私资料,审核往往比较严格。为了避开这个麻烦,同时也为了让通知更具实时性,我选择了钉钉机器人和企业微信机器人。 管理员或运营人员只需将机器人的 Webhook 地址配置好,每天早上一上班,就能在手机上收到一份清晰的到期清单。 价值:买到了最宝贵的“时间” 这个功能上线后,我们最直接的收获就是**“沟通时间”**: 提前预判:有了 7 天的缓冲期,运营人员可以气定神闲地与客户沟通续费。 提前操作:运维人员有了充裕的时间更换新证书,彻底杜绝了“半夜修证书”的尴尬。 技术实现 整个域名检测分为两部分:小程序端和服务端,小程序端负责提交域名信息以及Webhook(钉钉、企微)信息。以及查看导入的域名。服务端需要存储域名以及Webhook。并在每天的固定时刻进行域名巡检,当域名证书有效期小于7天时,将发送通知给管理员。 小程序端主要是常规的信息提交,我们来看下后端的Go代码: // 域名证书检测计划任务,每天固定6点执行 func CronCertCheck() { logger.Info("启动域名证书检测计划任务") // 获取用户webhook,有webhook则进行域名检测 webhooks, err := dao.GetAllWebhook() if err != nil { logger.Error("计划任务域名证书检测加载Webhooks错误,", err) return } // 循环扫描 for _, v := range webhooks { uid := v.Uid url := v.Url ht := v.HookType // 获取单个用户的域名 dbDomains, err := dao.GetWDomainByUid(uid) if err != nil { logger.Error(uid, "计划任务域名证书检测获取域名列表错误,", err) continue } if len(dbDomains) == 0 { logger.Error(uid, "计划任务域名证书检测用户域名数量为0") continue } // 解密URL地址为WEBHOOK password := fmt.Sprintf("domain:%d:%d", uid, ht) webhook, err := cryptoutil.AesDecryptWithPassword(url, password) if err != nil { logger.Error(uid, "计划任务域名证书检测webhook解密失败,", err) continue } // 获取域名列表,需要去重,用户是可以上传重复域名的。 domains := make([]string, 0) domainMap := make(map[string]bool, 0) for _, n := range dbDomains { name := n.Name isExist := domainMap[name] if isExist { continue } domains = append(domains, name) domainMap[name] = true } // 开始批次检测,并将检测结果返回 chkResult := CheckDomainsCert(domains, nil) // 如果结果不为空,则发送webhook通知 if len(chkResult) > 0 { notifyCfg := NotifyConfig{ Type: ht, URL: webhook, RetryTimes: 3, RetryDelay: 5, } result := NotifyContent{ Title: "以下域名的SSL证书将在7天内过期:", Domains: chkResult, } go SendWebhookNotificationWithRetry(notifyCfg, result) } // 休息1秒 time.Sleep(1 * time.Second) } logger.Info("域名证书检测处理:", len(webhooks)) } 在小程序上线后,用户使用极少,我不知道具体原因,可能是不信任功能,Webhook不想暴露,域名不想暴露?我又开发了客户端工具,纯本地运行,使用Wails3开发,是图形化桌面客户端,域名导入和通知都是在本地运行。并且增加了很多功能。例如支持HTTP/3,支持系统托盘,支持开启启动,支持导入更多域名,支持更多Webhook通知,监控面板更精细等。 ...

2018-01-10 · 2 min · Eagle

实战笔记:为了不再发错下载链接,我给工具箱加了“扫码鉴定”

小工具可以解决大问题。在软件发布流程中,最尴尬的事情莫过于:宣传图已经发出去了,用户扫码后却发现链接打不开,或者下载了一个旧版本的安装包。 我就亲身经历过这么一次“翻车”事故。 事故现场:一个 URL 引起的麻烦 有一次,我在更新软件下载页面后,由于换了一个新的 URL 地址,在生成二维码阶段没有进行严格校验,就直接把二维码发布了出去。 结果当用户兴冲冲地扫码下载时,发现软件运行异常。我对比了半天才发现,生成二维码时填写的 URL 链接版本号写错了一个字符,导致用户下载到了一个有问题的版本,这是偶然中的极端。 虽然这只是一个低级错误,但它让我意识到:在二维码挂载后、正式发布前,必须有一个极简的“内容鉴定”环节。 痛点:如何快速、无痛地校验? 通常我们校验二维码,习惯性地直接拿手机扫一下。但如果二维码指向的是一个大容量的 APP 下载包或复杂的跳转链接,扫码后手机会自动触发下载或进入复杂的网页路径。 我其实并不想真的下载并安装测试(那是后续的 QA 环节),我只是想一眼看到二维码里到底写了什么字符,确定那个 URL 是不是我想要的那一个。 实现:借力小程序的原生能力 既然发现了痛点,解决起来就非常顺手了。得益于微信生态对扫码的深度支持,我在“豆子工具”里实现了一个“二维码识别”功能: 核心逻辑:直接调用小程序的 wx.scanCode 接口。 功能表现:不仅支持扫描实物二维码,还支持从手机相册里直接读取生成的二维码图片,还支持小程序码扫描,支持查看页面路径和scene值。 结果反馈:系统不会触发自动跳转,而是将二维码包含的原始文本、URL、一维码内容直接以纯文本的形式展示在屏幕上。 价值:多看一眼,少出一次错 现在,每当我生成一个新的二维码,无论是用于软件下载、文档分享还是活动跳转,我都会习惯性地用自己的工具“扫一下”: 确认 URL 参数是否完整。 确认是否有肉眼难以察觉的拼写错误。 确认二维码的类型(一维码还是二维码)是否符合预期。 技术实现 使用小程序纯原生实现,仅仅做一个页面展示即可。 scanQRCode: function () { const that = this; wx.showLoading({ title: '识别中...', mask: true, }) wx.scanCode({ onlyFromCamera: false, scanType: ['qrCode', 'barCode', 'datamatrix', 'pdf417', 'wxCode'], success: (res) => { wx.hideLoading() if(res.scanType=='WX_CODE'){ that.setData({ scanResult: res.path }); }else{ that.setData({ scanResult: res.result }); } }, fail: (err) => { wx.hideLoading() console.error(err); } }); }, 需要注意的是,小程序码和其它码的结果字段有点不同,所以需要区分,以便取到正确的值。 ...

2018-01-09 · 1 min · Eagle

实战笔记:从一个按钮到全能生成器,随机数功能的“进化论”

这个工具是我的图腾,还记得我之前提到的吗?2018 年,“豆子工具”的初版只有一个功能:点一下按钮,生成一个随机数。 那时候的代码逻辑极其简单,甚至算不上一个“工具”。但随着这几年自己在开发、运维过程中不断遇到“需要一个复杂密码”、“需要一个 16 位纯金钥”或者“需要一个全大写 ID”等实际场景,我发现简单的随机数其实并不简单。 痛点:单一随机数的局限 早期的随机数功能非常死板,生成的格式往往不是我想要的。每次生成后,我可能还需要手动去改长度、改大小写,甚至手动补上特殊字符。 作为一个开发者,如果一个工具不能让我“一键到位”,那就是不合格的。 进化:高度自定义的“融合模式” 于是,我把这些年所有关于“随机”的需求全部揉碎、重组,将它升级成了一个全能的生成器。 现在的随机数功能,不再是盲目地随机,而是基于规则的精准生成。我为其设计了一套组合逻辑: 长度自定义:不再局限于几位数字,用户可以根据需求自由输入长度。 字符集自由组合: 纯数字:适用于验证码类场景。 纯字母:适用于临时 ID 或变量命名。 字母+数字:兼顾强度与易读性。 含特殊字符:专门为高强度随机密码设计。 结果格式化: 支持结果一键转大写或转小写。这在配置某些特定系统的 API Key 时非常有用,省去了手动切换输入法的麻烦。 我经常使用它生成一些随机数作为密码,密钥。我不用去数长度,或者生成后,我简单调整一下,让它有一点意义,我再去使用。 它使用小程序本地实现,下面是一段核心代码,描述了如何去实现随机数: // 生成随机字符串 genRndStr(rtype) { const that = this; if (utils.isEmpty(that.data.input1)) { wx.showToast({ title: '内容为空', }) return } let result = ''; switch (rtype) { case 1: // 纯数字 result = utils.randNumber(that.data.input1); break; case 2: // 纯字母 result = utils.randLetter(that.data.input1); break; case 3: // 字母和数字 result = utils.randStr(that.data.input1); break; case 4: // 含特殊字符 result = utils.randSymbol(that.data.input1); break; } that.setData({ result1: result }) }, 思考:小功能里的“产品观” 虽然随机数在技术实现上只是简单的字符数组随机采样,但从产品角度看,它体现了一种**“减少用户操作”**的原则。 ...

2018-01-07 · 1 min · Eagle

实战笔记:知识真的就是金钱,聊聊我的局域网调试工具

在“豆子工具”的所有功能中,局域网调试工具(TCP/UDP/WebSocket) 并不是受众最广的,但它却是我最引以为傲的一个。 因为它让我真切地体会到了一句话:知识就是力量,知识就是金钱。 缘起:把调试器揣进口袋 做嵌入式开发或者网络协议调试的人都知道,以往测试局域网通信,必须背着电脑,接上串口线或网线,蹲在机柜旁守着。我当时就在想:既然微信小程序已经开放了网络通信的能力,为什么我不能做一个随身携带的调试器呢? 于是,我深度调用了小程序的 wx.createTCPSocket、wx.createUDPSocket 和 WebSocket API,在“豆子工具”里构建了一个完整的网络测试模块。它支持: 十六进制(Hex)与 ASCII 码切换 实时数据日志展示 局域网内稳定的数据收发 变现:从技术分享到“第一桶金” 工具做成后,我并没有藏着掖着,而是把实现原理和核心逻辑整理成文,发布到了微信开发者社区。 没过多久,一位用户通过那篇文章联系到了我。他在工业自动化领域遇到了一个难题:需要一套能够定制化采集设备数据的微信小程序工具,而协议正是基于 Modbus/TCP。 由于我的“豆子工具”已经打好了坚实的底层基础,我对原有的 TCP 调试模块进行了针对性的修改,迅速就适配出了满足他实际业务场景的采集方案。 虽然这笔订单带来的“第一桶金”数额并不算惊人,但它对我的意义非凡。它验证了一个逻辑:当你把一个细分领域的工具做到极致,并愿意分享出去时,价值自然会找上门来。 技术实现 这个工具是纯小程序实现,它调用微信小程序的网络API。 doConnServer() { const that = this; wx.getNetworkType({ success(res) { const networkType = res.networkType if (networkType !== 'wifi') { wx.showToast({ title: '请打开WIFI', }) return } that.connTcp(); } }); }, connTcp() { const that = this; if (!utils.isEmpty(t)) { t.offMessage() t.offConnect() t.offError() t.offClose() t.close() t = null; that.setData({ isConn: false, tips: "关闭成功" }) wx.showToast({ title: '关闭成功', }) return } else { let ip = that.data.ip; let port = that.data.port; if (!utils.isValidIP(ip)) { wx.showToast({ title: '无效IP地址', }) return } if (!utils.isPrivateIP(ip)) { wx.showToast({ title: '请使用私有IP', }) return } if (utils.containInvalidPort(port)) { wx.showToast({ title: '非法端口', }) return } t = wx.createTCPSocket() t.onError(that.tErr) t.onConnect(that.tConn) t.onMessage(that.tMsg) t.onClose(that.tClose) t.connect({ address: ip, port: port, timeout: 3, }) } }, 这是部分核心TCP代码,它是TCP创建连接的核心代码。完整代码请从下面地址中的豆子碎片小程序内下载:https://91demo.top/tools/ ...

2018-01-06 · 1 min · Eagle