简介:这套WIFI共享小程序独立版源码是面向有意开展本地共享WiFi商业化运营的个人与团队的完整解决方案。源码从平台搭建到商业落地均有覆盖,既包含小程序前后端技术实现,也提供团长、拓展员、商家等多角色运营思路,并针对打印机等线下设备给出选购与部署指导,帮助非技术背景用户降低入门门槛。资源包共收录2004个文件,以PHP后端逻辑、JavaScript交互脚本、JSON数据配置、jpg/png图片素材及CSS样式为主,整体体积约68.07MB,目录结构清晰。目前已有198人学习浏览,适合正在筹备共享WiFi项目或希望快速上线自有小程序的技术人员与运营者。通过这套源码,使用者可获得可直接部署的程序代码、平台配置要点以及对运营环节的框架性指导,便于从零构建并持续迭代本地化共享WiFi生态。
1. 先搞明白:这版WIFI共享小程序独立版源码,值得你花一下午折腾吗?
如果你开咖啡店、运营酒店或者管着一层楼的公司网络,大概率每天都会被问十次“WiFi密码是多少”。我拿到这套 WIFI 共享小程序独立版源码时,第一反应是终于不用再贴一张手写密码的 A4 纸了。简单说,它是一套前后端都齐全的微信小程序工程:用户端负责展示、搜索和提交 WiFi 信息,后端提供数据接口和管理后台,管理员可以审核、编辑、置顶条目。它解决的是“访客自助拿密码”的问题,而不是去抢别人的网络。适合的受众非常明确:想给门店或园区做访客网络服务的中小商家、做信息化服务的接单开发者,以及想拿小程序练手的初学者。一份源码能不能落地,关键看它是否把“提交 - 审核 - 展示 - 分享”这条链路做完整了。我拆解后发现,这套资源在数据表和接口设计上比一般 demo 更接近可上线状态,但也有几个必须自己补的坑,下面从目录结构开始一层层讲。
2. 拆源码:独立版小程序的前后端分工、数据表设计与核心接口
2.1 目录结构与启动入口:从配置文件看清技术栈
拿到源码包后,先别急着导入微信开发者工具。我习惯先把项目根目录扫一遍,看依赖和配置文件,这样能快速判断整套东西的技术栈和运行条件。这套源码的目录大致是下面这个样子:
wifi-share-miniapp/ ├── miniprogram/ # 小程序前端 │ ├── pages/ │ │ ├── index/ # WiFi列表首页 │ │ ├── detail/ # WiFi详情与二维码 │ │ └── submit/ # 共享WiFi提交页 │ ├── app.js │ ├── app.json │ └── utils/ ├── server/ # Node.js后端 │ ├── app.js │ ├── config.js │ ├── routes/ │ └── controllers/ ├── admin/ # Web管理后台(简版) │ ├── index.html │ └── api.js └── sql/ └── init.sql # 数据库初始化脚本miniprogram是微信小程序前端,原生语法,没有引入 uni-app 或 Taro,这意味着它只依赖微信基础库,改起来负担很小。server是 Node.js + Express 的接口服务,负责读数据库、生成二维码、处理用户提交。admin是一个极简的浏览器后台,管理审核操作时直接请求后端接口。sql/init.sql是整份资源的灵魂,后面部署时能不能一次跑通,全看这张表设计得怎么样。
我一般会先用一条命令快速看后端依赖:
cd server && cat package.jsonpackage.json里通常会有express、mysql2、qrcode、jsonwebtoken这几个核心依赖。mysql2决定了数据库必须用 MySQL 5.7 以上版本,qrcode是后端生成 WiFi 二维码图片用的,jsonwebtoken负责管理员的登录态签发。如果你在 package.json 里看到cors,说明后端已经处理了跨域,管理后台放在不同端口也能直接调接口。依赖版本不用刻意升级到最新,保持源码自带的版本反而更稳,因为后面换 Node 大版本时容易遇到 MySQL 认证插件不兼容的问题,这一点在第 4 章还会细说。
2.2 数据库模型:WiFi信息表、用户表与分享记录表怎么设计
所有“共享”类小程序的核心,本质是一张带地理位置和状态标记的内容表。这套源码的init.sql里最关键的几张表是这样的:
CREATE TABLE `wifi_info` ( `id` int NOT NULL AUTO_INCREMENT, `ssid` varchar(64) NOT NULL COMMENT 'WiFi名称', `password` varchar(64) DEFAULT '' COMMENT 'WiFi密码,可为空', `location` varchar(255) NOT NULL COMMENT '位置描述', `latitude` decimal(10,6) DEFAULT NULL COMMENT '纬度', `longitude` decimal(10,6) DEFAULT NULL COMMENT '经度', `description` varchar(512) DEFAULT '' COMMENT '补充说明', `qr_code` varchar(255) DEFAULT '' COMMENT '二维码图片URL', `status` tinyint NOT NULL DEFAULT '0' COMMENT '0待审核 1已通过 2已下架', `share_count` int NOT NULL DEFAULT '0' COMMENT '分享次数', `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_status_created` (`status`, `created_at`), KEY `idx_location` (`latitude`, `longitude`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;ssid和password是实际连接需要的信息,这里要留意:密码字段设计为可空,意味着有些开放网络没有密码,前端要针对空密码显示“开放网络”而不是傻乎乎地让用户填一个空字符串。status字段做了三态设计,这是能和“审核流”配合的关键。如果这套源码只有一个 0/1 字段,那它只能做展示,做不了审核。idx_status_created是列表查询的主要索引,因为首页只会查status=1的数据,idx_location是为后续做“附近WiFi”准备的,虽然现在默认接口没接距离计算,但表结构已经留了余地。
用户表与分享记录表是给“裂变”和“风控”用的:
CREATE TABLE `user` ( `id` int NOT NULL AUTO_INCREMENT, `openid` varchar(64) NOT NULL COMMENT '微信openid', `nickname` varchar(64) DEFAULT '', `avatar` varchar(255) DEFAULT '', `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_openid` (`openid`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE `share_log` ( `id` int NOT NULL AUTO_INCREMENT, `wifi_id` int NOT NULL, `openid` varchar(64) NOT NULL, `channel` varchar(16) DEFAULT 'scene' COMMENT '分享渠道:scene/group', `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, KEY `idx_wifi_id` (`wifi_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;user表用openid做唯一键,避免同一个微信用户反复提交时产生垃圾数据。share_log是埋点表,每次用户把某个 WiFi 条目转发给微信群时,小程序端调一次接口写一条记录。这套源码的管理后台里就有一个“分享榜”页面,统计逻辑就是从这张表group by wifi_id出来的。在设计上要注意:share_log只做插入不做更新,因为它是流水数据,更新反而容易出错。
2.3 核心接口逻辑:列表查询、提交分享与二维码生成
后端接口的/api/wifi/list是最常用的路径,我贴出核心实现片段,方便你看懂它是怎么把“审核通过”和“排序”串起来的:
// server/controllers/wifi.js const db = require('../db'); exports.list = async (req, res) => { const page = parseInt(req.query.page) || 1; const size = parseInt(req.query.size) || 10; const offset = (page - 1) * size; const [rows] = await db.execute( `SELECT id, ssid, location, description, share_count, created_at FROM wifi_info WHERE status = 1 ORDER BY share_count DESC, created_at DESC LIMIT ? OFFSET ?`, [size, offset] ); const [[{ total }]] = await db.execute( `SELECT COUNT(*) AS total FROM wifi_info WHERE status = 1` ); res.json({ code: 0, data: { list: rows, total, page, size, }, }); };分页用了LIMIT ? OFFSET ?,mysql2会自动把参数转成整数,不用手动拼接字符串,能避免注入。排序上share_count DESC优先,这等于让热门 WiFi 排前面,但同时也是“马太效应”的来源——新提交的 WiFi 永远排后面。如果你想把新条目也露出来,可以改成ORDER BY (share_count * 0.3 + timestampdiff(HOUR, created_at, now()) * 0.7) DESC,但大多数人用默认逻辑就够了。
提交接口是另一个重点,它会在后端完成校验和二维码生成:
// server/controllers/wifi.js const QRCode = require('qrcode'); exports.add = async (req, res) => { const { ssid, password, location, latitude, longitude, description } = req.body; if (!ssid || !location) { return res.json({ code: -1, msg: 'WiFi名称和位置不能为空' }); } const wifiString = `WIFI:T:WPA;S:${ssid};P:${password};;`; const qrDataUrl = await QRCode.toDataURL(wifiString); const qrCodePath = `/uploads/qr_${Date.now()}.png`; // 实际项目会把 qrDataUrl 写入文件或上传到对象存储 await db.execute( `INSERT INTO wifi_info (ssid, password, location, latitude, longitude, description, qr_code, status) VALUES (?, ?, ?, ?, ?, ?, ?, 0)`, [ssid, password, location, latitude || null, longitude || null, description, qrCodePath] ); res.json({ code: 0, msg: '提交成功,等待审核' }); };很多新手不理解为什么WIFI:T:WPA;S:xxx;P:xxx;;这一串这么重要。这串是标准的 WiFi 分享配置字符串,手机相机扫码后能自动弹出“加入网络”的提示。后面我把这段二维码方案改造得更灵活,支持WPA/WEP/nopass三种类型,第 5 章会说到。需要注意:status被硬编码成 0,也就是说用户提交后不会立刻出现在列表里。如果店铺老板自己就是唯一管理员,完全可以把这个默认值改成 1,省掉审核步骤。这个开关放在源码注释里通常写着“上线前请改成审核模式”。
3. 本地跑起来:从零初始化数据库到小程序显示WiFi列表
3.1 后端环境准备:Node.js版本、依赖安装与启动参数
先把环境装对,再动手跑代码。我强烈建议你用 Node.js 14 或 16 LTS 版本,不要一上来就装 Node 18。原因是这套源码的mysql2依赖如果锁定在老版本,在 Node 18 上会出现caching_sha2_password认证插件不支持的问题,到时候报错会非常痛苦。可以用nvm装指定版本,然后进到server目录安装依赖:
cd server npm install cp .env.example .env # 如果源码提供了示例配置npm install之后要检查node_modules里是否真的生成了mysql2、express等文件夹,不要只看终端输出“added 100 packages”就完事。因为有的国内网络环境会设置 npm 镜像源,个别包安装失败回滚时终端显示成功,实际目录里缺文件。装完依赖我看一眼配置:
cat .env配置文件里常见的字段有:
DB_HOST=127.0.0.1 DB_PORT=3306 DB_USER=root DB_PASS=你的密码 DB_NAME=wifi_share JWT_SECRET=随便一串长字符串 PORT=3000DB_PASS如果含特殊字符,比如@、#、空格,一定记得放在引号里,否则 Node 读取环境变量时会把它截断。另外PORT=3000要用没有被占用的端口。如果你本机装了其他服务占用了 3000,启动时会报EADDRINUSE,那就改成 3001。改了端口之后,小程序和后台的baseUrl都得跟着改。
3.2 初始化数据库:建库建表、种子数据与管理员账号
数据库初始化最有价值的文件是sql/init.sql。我建议不要直接在可视化工具里整份执行,而是分三步走,方便定位错误:
mysql -u root -p -e "CREATE DATABASE wifi_share DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;" mysql -u root -p wifi_share < sql/init.sql第一行先建库,重点是指定utf8mb4字符集。如果你漏了这一步,MySQL 默认可能是latin1,后面插入中文位置描述会乱码。第二行导入表结构。导入后可以验证一下表是否建全:
USE wifi_share; SHOW TABLES;正常会看到user、wifi_info、share_log三张表。接着要手动插入一条测试数据,因为这张表默认是空的,小程序列表页会一直转圈。插入时注意status要设成 1,否则列表接口根本查不到:
INSERT INTO wifi_info (ssid, password, location, latitude, longitude, description, qr_code, status) VALUES ('Cafe_5G', 'coffee123', '北京市朝阳区XX路1号', 39.90469, 116.40717, '前台旁边的路由器,密码带大小写', '/uploads/qr_test.png', 1);管理后台的默认账号密码通常在init.sql的末尾,一般是admin/admin123,也可能是admin/123456。登录机制用的是jsonwebtoken,第一次登录会返回一个 token 存在浏览器localStorage里。我建议登录后马上改密码,尤其是这个项目要部署到公网时,不然后端接口被人拿默认密码扫出来就是灾难。
3.3 小程序前端配置:开发者工具导入、AppID与本地IP调试
前端这块最考验环境配置。打开微信开发者工具,选择“导入项目”,目录指向源码里的miniprogram文件夹。注意不是根目录,而是含app.json的那一层。AppID 可以用测试号,但如果要用wx.login和用户真实 openid,就必须填自己的小程序 AppID。
导入后先改utils/config.js里的请求地址:
// utils/config.js module.exports = { baseUrl: 'http://127.0.0.1:3000', };本地调试时,微信开发者工具可以勾选“不校验合法域名”。但如果你用真机预览,这个选项不生效,真机请求http://127.0.0.1会直接失败。所以本地调试时,把你电脑的局域网 IP 填进去:
module.exports = { baseUrl: 'http://192.168.1.123:3000', };手机和电脑必须连同一个路由器,且在微信开发者工具里点击“真机调试”,这样手机才能通过局域网 IP 访问到你电脑上的后端服务。后端代码里如果用了cors(),默认允许所有来源,就不会有问题。但如果你把cors配置成只允许某个域名,本地调试时要把该域名加进去,否则浏览器控制台会报跨域错误。
启动后端的命令很简单:
cd server && node app.js看到控制台输出Server running on port 3000和MySQL connected,说明后端已经就绪。此时在小程序模拟器里下拉刷新,列表页应该能拿到刚才插入的测试 WiFi 数据。
4. 上线避坑:从开发工具到真机,五个常见的翻车现场
4.1 真机请求失败:域名白名单和HTTP地址被拦截
现象:模拟器里一切正常,列表能加载;一换到真机预览,页面直接提示“request:fail”,控制台显示http://192.168.1.123:3000不在合法域名列表。
原因:小程序真机环境强制校验业务域名,不能使用 IP、不带端口、必须 HTTPS。这是微信平台的硬性限制,和源码无关。
解决:如果你只是内部体验,可以在开发者工具右上角“详情 → 本地设置”里勾选“不校验合法域名”,并打开“真机调试”。注意“真机调试”也不是百分百稳定,最根本的办法是买一台云服务器和备案过的域名,配置 Nginx 反向代理到后端的 3000 端口,然后在微信公众平台里把https://api.你的域名.com加入“request合法域名”。这步不要拖到上线前才做,因为域名备案可能要一周。
4.2 二维码在部分手机上打不开:URL编码与特殊字符
现象:iOS 微信扫码能正常弹出 Wi-Fi 连接,Android 手机扫码后提示“格式不支持”,或者二维码里的密码少了几位。
原因:密码或 SSID 里包含空格、&、=、中文等特殊字符,这些字符在生成二维码的数据串中没有做 URL 编码,手机解析时会把它截断。比如密码是a&b=123,生成的WIFI:T:WPA;S:xxx;P:a&b=123;;里&会被当成参数分隔符,导致密码变短。
解决:在生成二维码前,对 SSID 和密码做一次encodeURIComponent处理,但注意不要对整个WIFI:配置串编码,只编码S:和P:后面的值。同时,如果 SSID 本身是中文,某些老手机不支持 GBK 编码,建议在详情页额外显示一段明文密码,给用户手动输入的机会。我后来在这次源码的基础上加了一个“手动输入密码”的折叠栏,就不怕任何手机扫码失败了。
4.3 管理后台登录后空白:Cookies跨域与Session配置
现象:管理后台登录成功后,跳转到列表页,页面一直白屏。打开 F12 看到/api/auth/me接口返回 401。
原因:管理后台和登录页不在同一个端口或域名,后端没有正确下发 CORS 头,浏览器存储的 token 无法通过credentials: 'include'传过去。
解决:检查后端的 CORS 配置,把管理后台的地址显式加进去:
const cors = require('cors'); app.use(cors({ origin: ['http://localhost:8080', 'http://192.168.1.123:8080'], credentials: true, }));需要确保前端fetch请求带了credentials: 'include'。但这里更推荐使用 Authorization Header 而不是 Cookie,因为 JWT 本身就是一个无状态的 token。如果源码里的管理后台是用axios的,把withCredentials: true打开即可。如果还是空白,检查浏览器控制台里有没有 JS 报错,常见的是Cannot read property 'list' of undefined,那是后端接口返回了{ code: -1 },而前端没有做错误分支处理,属于源码的小瑕疵。
4.4 数据库中文乱码:连接字符集与表字符集不匹配
现象:小程序端正常插入的中文位置描述,在后端管理后台里显示成“???”或乱码。
原因:MySQL 连接字符串里缺少charset=utf8mb4配置,导致连接使用的字符集可能是latin1。即使表结构指定了utf8mb4,连接层不对,写入时仍会转成乱码。
解决:在server/db.js里创建连接池时显式指定字符集:
const mysql = require('mysql2'); const pool = mysql.createPool({ host: process.env.DB_HOST, user: process.env.DB_USER, password: process.env.DB_PASS, database: process.env.DB_NAME, charset: 'utf8mb4', }); module.exports = pool.promise();改完后重启后端,把旧的乱码数据删除再重新插入。注意charset: 'utf8mb4'如果不生效,检查一下 MySQL 配置文件里的character_set_server,必要时在连接池配置里加collation: 'utf8mb4_unicode_ci'。这条坑非常隐蔽,因为它不影响表结构,只影响写入,很多开发者会误以为是前端上传时编码错了。
4.5 审核被拒:WIFI共享需提供信息发布资质
现象:提交微信审核时,被提示“该类目需要提供相关资质”,或者“涉及用户生成内容”。
原因:只要允许用户提交 WiFi 信息,在微信的类目里就属于“信息发布平台”或“社区”,个人主体小程序通常没法过审。
解决:如果你是个人开发者,建议在小程序端去掉“提交 WiFi”入口,只保留“查看和搜索”功能,这样类目可以选择“工具 > 信息查询”或“生活服务 > 生活缴费”等宽松类目。如果是企业主体,可以申请“社交 > 社区”类目并提交相应的 ICP 备案和 《信息网络传播视听节目许可证》等资质。很多共享 WiFi 小程序上线前都会面临这个卡点,最快的方式是先做成只读查询工具,把审核通过后再灰度开放提交入口。
5. 进阶一:把源码改造成可用的“懒人连接”工具
5.1 服务端增加WiFi可用性检测:ICMP与HTTP探测
共享 WiFi 最大的问题是假数据。用户提交一个已经倒闭的店铺的网络,不仅浪费后来者时间,还会降低整个小程序的可信度。我在这份源码的后端加了一个定时扫描任务,对每个status=1的 WiFi 做可用性探测。探测方式分两种:内网环境和公网环境,但源码默认跑在公网,所以最通用的是 HTTP 探测——通过 WiFi 的出口 IP 判断网关是否存活是不现实的,我只能退而求其次,让管理员手工标记“确认可用”,同时用信号量做弱校验。
// server/controllers/wifi.js const net = require('net'); exports.checkReachable = async (req, res) => { const { ip } = req.body; if (!ip) return res.json({ code: -1, msg: '缺少IP' }); const start = Date.now(); const socket = new net.Socket(); socket.setTimeout(3000); socket.connect(80, ip, () => { socket.destroy(); res.json({ code: 0, ms: Date.now() - start }); }); socket.on('timeout', () => { socket.destroy(); res.json({ code: -1, msg: '连接超时' }); }); };这段代码的本质是“探测某个 IP 的 80 端口是否通”,不是真的检测 WiFi。它能过滤掉一部分填写了虚假网关 IP 的数据,但无法覆盖真实场景,比如路由器禁 ping 或禁外访。所以我把它定位成“人工审核辅助工具”:管理后台每条记录旁加一个“检测”按钮,管理员点一下,能通就放行,超时就标记降权。
参数说明:socket.setTimeout(3000)是 3 秒超时,超过就认为不可达;connect(80, ip)检测的是标准 HTTP 端口。如果你的目标网络屏蔽了 80 端口,可以改成443或53,实际运营中我更推荐检测53端口,因为 DNS 是大多数网络不会屏蔽的出口。
添加检测按钮后,wifi_info表需要加一个last_checked_at字段。我直接用一条 SQL 增加列:
ALTER TABLE wifi_info ADD COLUMN last_checked_at datetime DEFAULT NULL COMMENT '最近检测时间';列表接口同步把这个字段带出去,前端在详情页显示“昨天已验证”或“一个月前验证”,用户就能快速判断这个 WiFi 还活着没。
5.2 小程序端增加“一键复制密码”与自动填写逻辑
就算二维码能扫,还是会有很多人习惯复制密码去手动输入。我最初以为二维码解决一切,结果被现实打脸:店里灯光暗,扫码对不准,或者客人微信扫完弹窗被拦截。所以我在详情页加了一个“复制密码”按钮,同时把密码明文默认隐藏,点击小眼睛图标才显示。
// miniprogram/pages/detail/detail.js Page({ data: { showPassword: false, password: 'coffee123', }, togglePassword() { this.setData({ showPassword: !this.data.showPassword }); }, copyPassword() { wx.setClipboardData({ data: this.data.password, success() { wx.showToast({ title: '密码已复制', icon: 'none' }); }, }); }, });这里有一个容易被忽略的参数:wx.setClipboardData在复制成功后会自动弹出系统提示“内容已复制”,如果你不想显示系统提示,可以在success回调里延迟调wx.hideToast,但更省事的做法是不拦截,因为系统提示能明确告知用户操作成功。真正要注意的是:复制密码后,用户切到系统设置连接 WiFi,再切回小程序,前后台切换会触发onHide和onShow,这时页面数据有可能被重新加载,需要把密码缓存到本地。
// miniprogram/pages/detail/detail.js onLoad(query) { this.setData({ id: query.id }); }在onShow里从全局缓存读取密码,避免每次切换都重新请求详情接口:
onShow() { const cached = wx.getStorageSync('wifi_' + this.data.id); if (cached) { this.setData({ password: cached.password, ssid: cached.ssid }); } }缓存时同时存ssid和password,是为了在页面上显示“帮你复制了 XX 网络的密码”。这个小功能能极大提升老访客的复访率。
5.3 利用缓存减少请求:本地存储热点数据
列表页每次下拉刷新都会请求后端,虽然流量不大,但在咖啡馆这种人多、网络拥堵的场景下,接口响应会变慢。我做了一版按“最近三天热门”分类的本地缓存:首次请求成功后,把列表响应缓存在wx.setStorageSync,下次打开页面先渲染缓存,再静默请求新数据覆盖。
// miniprogram/pages/index/index.js const CACHE_KEY = 'wifi_list_cache'; Page({ onLoad() { const cached = wx.getStorageSync(CACHE_KEY); if (cached) { this.setData({ list: cached }); } this.fetchList(); }, fetchList() { wx.request({ url: getApp().globalData.baseUrl + '/api/wifi/list', data: { page: 1, size: 10 }, success: (res) => { if (res.data.code === 0) { this.setData({ list: res.data.data.list }); wx.setStorageSync(CACHE_KEY, this.data.list); } }, }); }, });为什么缓存体积要控制?微信小程序单个 key 的缓存大小限制是 1MB,一条 WiFi 信息包括 SSID、位置、描述可能占 300 字节,10 条没问题,但如果你把它改成无限滚动,缓存很容易超限。所以我只缓存第一页 10 条,并把描述字段截断到 50 个字符:
const shortDesc = item.description.length > 50 ? item.description.slice(0, 50) + '...' : item.description;setStorageSync是同步方法,可能在主线程产生阻塞,但列表数据量小,影响可忽略。如果你想更规范,用setStorage的异步版本更好。这套缓存逻辑跑了一段时间后,我发现一个问题:如果管理员在后台把某条 WiFi 下架了,用户本地缓存里还看得到,于是我在每个页面的onShow里加了一个safeRevalidate机制——列表页展示缓存超过 5 分钟就会强制重新请求,并删除旧缓存。
6. 进阶二:验证分享裂变效果,用埋点数据反推功能优化
6.1 埋点设计:分享次数、成功添加WiFi数、扫码连接率
把功能做出来只是第一步,分享类小程序最怕的是“有人转发,没人添加”。我给这套源码加了一套极简埋点,只统计三个关键数字:分享次数、新提交 WiFi 数、详情页访问到扫码的转化率。
分享次数在share_log表里已经具备,只需在小程序端捕获转发事件:
// miniprogram/pages/detail/detail.js onShareAppMessage() { const wifi = this.data.detailInfo; return { title: `我在这个WiFi小程序里找到了${wifi.location}的密码`, path: `/pages/detail/detail?id=${wifi.id}`, }; }这里有个参数坑:path必须写完整路径,且id要用等号连接,不能加多余的 query 参数。有的人写成?id=${wifi.id}&from=share,然后详情页onLoad里同时读from和id,结果id变成了字符串'123',数据库查询没问题,但如果你后续把id传给Math.ceil或parseInt时忘了转类型,就会埋雷。
成功添加 WiFi 数在接口层就能统计:POST /api/wifi/add接口每调用一次,往wifi_info表插入一条status=0的数据,后台审核通过后,status变为 1。所以“审核通过率”就是新增数除以添加总数。我在管理后台的统计面板上把这个比例列出来了,低于 50% 说明前端引导做得太差,用户填了一堆垃圾数据。
6.2 用微信的onShareTimeline做朋友圈分享适配
微信对分享朋友圈的支持比较特殊,只有onShareTimeline能触发。我一开始没加这个函数,结果用户只能转发到群聊,无法发朋友圈,流量少了一半。补上这个适配后,朋友圈卡片右上角会显示一个小程序按钮,点击进入详情页:
onShareTimeline() { const wifi = this.data.detailInfo; return { title: `${wifi.location} 的WiFi密码,扫码直达`, query: `id=${wifi.id}`, imageUrl: wifi.qr_code, }; }注意onShareTimeline的query不需要写/pages/detail/detail,只需要参数部分。imageUrl建议用详情页的二维码图片,这样用户第一眼就知道这是能扫的。如果imageUrl不设置,微信会默认截取页面截图,效果很一般。
朋友圈分享没有“分享回调”,也就是说我们无法知道用户是否真的发出去了。这很正常,别纠结,只需要把share_log的channel字段值从scene改成timeline做一个粗略统计即可。真正的转化数据在详情页:从share_log关联wifi_id,再关联wifi_info,如果某条 WiFi 的分享次数高、但详情页访问量低,说明卡片文案不够吸引人;如果详情页访问量高、扫码率低,说明二维码在部分手机上失效,优先排查第 4.2 节的编码问题。
6.3 小习惯:每次改动后强制走一遍的验证流程
这套源码我改了不下十次,踩过的坑让我形成了一套强制验证流程。每次改完代码,我不再直接发版本,而是先跑一版“冒烟清单”:
先启动后端,用curl打一下列表接口和新增接口,确认返回code: 0。然后打开开发者工具,在模拟器里走一遍完整路径:进入首页 → 点详情 → 复制密码 → 转发到“文件传输助手”→ 再点回来。这个转发回环测试能暴露大部分路由参数和缓存问题。最后用真机扫码测一次二维码,特别是密码带&或中文的测试用例,我会在提交接口里临时插入两条特殊数据,扫完确认手机弹窗正常再删掉。
从那以后,我每次改完代码都强制走一遍这三步,不到两分钟,但能挡住九成翻车。这套 WIFI 共享小程序独立版源码最大的价值不在于它本身多完美,而在于它的表结构和接口组织方式足够标准,你可以像我一样在它上面不断加料:可用性检测、埋点、缓存、特殊字符处理,每加一层都会离“真正能运营的产品”更近一步。希望这个拆解过程对你上手这套源码有帮助。
本文还有配套的精品资源,点击获取