
智慧党建系统全解析:从桌面系统到「网页版 + 小程序」双端(项目综述)
一、项目背景:从内网桌面系统说起
二、技术演进:桌面 → 云开发 → 自建服务器
三、功能模块(12 个页面 / 8 大业务场景)
四、数据迁移:SQLite 主键转字符串 _id
五、多用户数据隔离设计
六、党员发展 25 步时间线提醒
七、整体架构:一后端双前端
八、核心:request.js 请求层
九、后端 /api/mp 统一入口(24 个 action)
十、微信一键登录与账号绑定
十一、数据模型与 ID 映射
十二、演示账号设计(只读 + 不绑微信)
十三、小程序部署教程(手把手)
十四、网页版部署:一键脚本 + 服务器
十五、网页版 vs 小程序版
十六、踩坑记录与注意事项
十七、总结
十八、源码下载(评论后可见)
一、项目背景:从内网桌面系统说起
单位之前有一个基于 Vue 3 + Node.js + SQLite 的智慧党建管理系统,运行在内网服务器上,用于党员管理、三会一课、主题党日、党员发展全周期追踪等工作。系统功能完善,但有两个痛点:
- 使用场景受限:只能在内网电脑上访问,领导外出调研、组织生活现场等移动场景用不了;
- 数据录入不便:党员信息、会议记录等都需要回到办公室电脑上操作。
于是有了「移动化 / 小程序化」的需求:把桌面系统搬到随时随地可用,同时复用已有的业务数据(SQLite 数据库里有 700+ 条真实数据)。
二、技术演进:桌面 → 云开发 → 自建服务器
这个项目经历了 三轮架构迭代,每一步都踩了不少坑,也沉淀了不少经验:
2.1 第一轮:内网桌面系统(Vue3 + Express + SQLite)
最初的网页版跑在内网,功能完善但只能在办公室用。
2.2 第二轮:微信小程序 + 云开发(CloudBase)
为了让系统移动化,用 uni-app + 微信云开发 把桌面系统搬进了微信小程序。云函数 + 云数据库 + 云存储,免运维确实爽。但随着用户量增长,云开发的限制越来越明显:
- 免费额度有限:数据库读写次数、云函数调用次数都有上限,超量就要付费;
- 数据不互通:网页版跑在自有服务器(Node.js + SQLite),小程序数据在微信云,两套数据无法打通;
- 云函数冷启动慢:首次调用有 1-3 秒延迟,体验不佳;
- 平台锁定:业务逻辑绑定在微信云上,无法迁移到其他平台。
2.3 第三轮:弃用云开发,小程序直连自建服务器
项目里已经有一套完整的 Express + SQLite + JWT 网页版后端,于是做了一个关键决策:弃用云开发,小程序直连自建服务器后端,与网页版共用同一套后端、同一个数据库。
三、功能模块(12 个页面 / 8 大业务场景)
系统完整复刻了桌面系统的核心功能,共 12 个页面,覆盖 8 大业务场景:
| 模块 | 功能 |
|---|---|
| 登录注册 | 账号密码登录、微信一键登录、开放注册(每人独立数据空间) |
| 工作台 | 6 项数据统计 + 功能导航 + 最近会议/活动 |
| 党员管理 | 党员列表、按组织筛选、搜索、增删改查、党员详情 |
| 组织架构 | 党总支 → 党支部树形展示、党员数统计 |
| 三会一课 | 会议列表、详情、新增 |
| 主题党日 | 活动卡片展示、新增 |
| 党员发展 | 25 步标准流程时间线 + 逾期/到期自动提醒 |
| 理论学习 | 中心组学习记录、新增 |
| 工作档案 | 文件分类浏览、搜索、分页 |
| 待办事项 | 优先级标签、完成勾选、增删 |
四、数据迁移:SQLite 主键转字符串 _id
桌面系统的数据在 SQLite 数据库里,需要迁移到云端(云数据库或自建服务器)。这里有一个关键的坑:外键关联。
⚠️ SQLite 数字主键 → 字符串 _id 的转换规则:
- 主键
id→ 转成字符串作为_id(保证外键可关联) - 外键
org_id/mentor_id→ 同步转成字符串 - 数组字段(参会党员
attendees等)→ 元素转字符串
// 迁移工具核心逻辑
// 1. 通过服务器 API 登录获取 token
// 2. 分页拉取全量数据(members、archives 等)
// 3. 树形结构扁平化(orgs 的 children 展开)
// 4. 剔除联表派生字段(org_name / attendee_count 等)
// 5. 生成导入所需的 data/*.json
// 导入时(doc(_id).set() 精确控制 _id)
function toCloudDoc(collection, item) {
const { id, ...rest } = item;
const doc = { ...rest };
if (id !== undefined) doc._id = String(id); // 主键转字符串
if (collection !== 'users') doc.owner_id = 'admin'; // 数据归属
// 外键/数组字段转字符串...
return doc;
}
迁移后的数据规模(示例):
| 集合 | 记录数 |
|---|---|
| members(党员) | 46 |
| orgs(组织) | 4 |
| meetings(三会一课) | 3 |
| activities(主题党日) | 8 |
| archives(工作档案) | 614 |
| study / todos / development / users | 6 / 7 / 1 / 1 |
五、多用户数据隔离设计
系统采用开放注册 + 每人独立数据空间的模式:任何人可注册账号,注册后拥有自己的一套数据(党员、会议、档案等),互不可见。
实现原理:所有业务集合增加 owner_id 字段,后端在查询和写入时强制按当前用户过滤:
// 统一鉴权与数据隔离
function buildQuery(collection, params) {
const where = {};
if (params.username) {
where.owner_id = params.username; // 只查自己的数据
}
return where;
}
// 写入时强制带上 owner_id
const data = { ..., owner_id: currentUser.username };
await db.collection('members').add({ data });
// 更新/删除时校验归属(防止越权操作)
const res = await db.collection('members')
.where({ _id: event.id, owner_id: currentUser.username })
.update({ data });
六、党员发展 25 步时间线提醒
这是最有业务价值的模块。依据《中国共产党发展党员工作细则》,发展党员分为 5 个阶段 25 个步骤,每个关键节点都有时限要求。系统自动计算每个人的进度并提醒:
| 阶段 | 步骤数 | 关键时限 |
|---|---|---|
| 一、申请入党 | 2 | 党组织收到申请后 1 个月内谈话 |
| 二、积极分子确定和培养教育 | 4 | 申请满 6 个月可确定积极分子;培养考察 至少 1 年 |
| 三、发展对象的确定和考察 | 5 | 政治审查、集中培训(≥3 天/24 学时) |
| 四、预备党员的接收 | 7 | 党委审批 3 个月内(可延至 6 个月) |
| 五、预备党员教育考察和转正 | 7 | 预备期 1 年、转正审批 3 个月内 |
// 时间轴计算核心(timeline.js)
// 25 个节点按阶段定义,每个节点有基准日期与偏移天数
const FLOW = [
{ key: 'apply', name: '递交入党申请书', stage: 1, offset: 0 },
{ key: 'talk', name: '党组织派人谈话', stage: 1, offset: 30, desc: '收到申请后 1 个月内' },
{ key: 'activist', name: '推荐和确定入党积极分子', stage: 2, offset: 180 },
{ key: 'inspect', name: '培养教育考察', stage: 2, offset: 365, desc: '至少 1 年' },
// ... 共 25 个节点
];
// 每个节点计算:目标日期、剩余天数、状态
// status: done(已完成) / soon(30天内到期) / overdue(已逾期) / pending(待完成)
// 关键设计:前置节点未完成则不计算后续提醒(防误报)
页面效果:
- 按 5 个阶段分组展示,每组显示完成进度(如 4/5)
- 🔴 已逾期节点标红、🟠 即将到期(30 天内)标橙、🟢 已完成打勾
- 顶部汇总「X 项逾期 / Y 项即将到期」,并提示下一步节点
- 点击未完成节点可一键标记完成(自动回填日期、推进阶段)
七、整体架构:一后端双前端
| 端 | 技术栈 | 通信方式 |
|---|---|---|
| 微信小程序 | uni-app 3.x + Vue 3 + Vite | POST /api/mp(action 分发) |
| 网页版 | 原生 HTML + JS | REST:/api/data /api/login 等 |
| 后端(共用) | Express + better-sqlite3 + JWT | — |
| 数据库(共用) | SQLite(zhdj-web.db) | users + user_data 两张表 |
架构图非常简洁——没有微服务,没有消息队列,就是一台服务器 + 一个数据库 + 两套前端:
┌─────────────┐ ┌──────────────┐ ┌──────────────┐
│ 微信小程序 │ ───▶ │ │ │ │
│ (uni-app) │ │ Express 后端 │ ───▶ │ SQLite 数据库│
├─────────────┤ │ /api/mp │ │ zhdj-web.db │
│ 网页版 │ ───▶ │ /api/data │ │ │
│ (原生 HTML) │ │ /api/login │ │ │
└─────────────┘ └──────────────┘ └──────────────┘
核心思想:小程序端把所有业务调用收敛到一个统一入口(POST /api/mp + action 参数),后端按 action 分发到对应处理器,这样小程序端不需要知道 REST 资源路径,只需传「我要干什么」。这与微信云函数时代 wx.cloud.callFunction 的调用方式保持了一致性,迁移成本极低。
源码目录结构:
智慧党建小程序/
├── src/ # 小程序源码(uni-app 工程)
│ ├── App.vue # 应用入口
│ ├── main.js # 框架初始化
│ ├── manifest.json # 小程序配置(填入自己的 AppID)
│ ├── pages.json # 12 个页面 + 4 个 tabBar
│ ├── utils/
│ │ └── request.js # ★ 核心请求层(129 行)
│ ├── pages/
│ │ ├── login/ # 登录/注册/微信绑定
│ │ ├── index/ # 工作台(数据概览 + 功能导航)
│ │ ├── members/ # 党员列表 + 详情
│ │ ├── orgs/ # 组织架构树
│ │ ├── meetings/ # 三会一课列表 + 详情
│ │ ├── activities/ # 主题党日
│ │ ├── development/ # 党员发展(25 步时间线)
│ │ ├── study/ # 理论学习中心组
│ │ ├── archives/ # 工作档案
│ │ └── todos/ # 待办事项(乐观更新勾选)
│ └── cloudfunctions/ # 已废弃的云函数(保留作迁移参考)
├── web/ # 网页版前端(单文件 HTML)
├── web-server/ # 后端(与双端共用)
│ ├── server.js # Express 主程序
│ ├── timeline.js # 党员发展 25 步计算
│ └── seed.json # 脱敏示例数据
└── README.md
cloudfunctions/ 目录是迁移前的云函数代码,已经不再被小程序调用,保留仅作为逻辑参考(后端 /api/mp 的 action 处理器就是从 api/index.js 平移过来的)。八、核心:request.js 请求层
这是小程序最关键的一个文件——所有请求的收发、鉴权、错误处理都在这里。核心设计:
// src/utils/request.js(精简版)
const API_BASE = 'https://YOUR_DOMAIN/api';
function callCloud(action, data = {}, funcName) {
return new Promise((resolve, reject) => {
const token = uni.getStorageSync('token');
const payload = Object.assign({ action }, data);
const handleResult = (r) => {
if (r && r.code === 0) {
resolve(r.data); // 成功:直接返回 data
} else {
const msg = (r && r.error) || '请求失败';
if (r && r.code === 401) {
// token 过期:清登录态,跳回登录页
uni.removeStorageSync('token');
uni.removeStorageSync('user');
uni.setStorageSync('skip_auto_login', true);
uni.showToast({ title: '登录已过期,请重新登录', icon: 'none' });
setTimeout(() => uni.reLaunch({ url: '/pages/login/login' }), 800);
} else {
uni.showToast({ title: msg, icon: 'none' }); // 其他错误:toast 展示后端 error
}
reject(new Error(msg));
}
};
// 登录/注册走 REST,业务统一走 /api/mp
if (funcName === 'login') {
if (action === 'register') {
request(API_BASE + '/register', 'POST', data, ...);
} else if (action === 'wechatLogin') {
// 微信登录:先 wx.login 拿 code,再换 openid
uni.login({
provider: 'weixin',
success: (loginRes) => {
request(API_BASE + '/wx-login', 'POST', { code: loginRes.code }, ...);
}
});
} else {
request(API_BASE + '/login', 'POST', data, ...);
}
return;
}
// 业务接口统一走 /api/mp,带上 JWT
request(API_BASE + '/mp', 'POST', payload, {
'content-type': 'application/json',
'Authorization': token ? 'Bearer ' + token : ''
});
});
}
// 对外暴露的所有业务方法(24 个)
export default {
auth: (action, data) => callCloud(action, data, 'login'),
wechatLogin: () => callCloud('wechatLogin', {}, 'login'),
getWorkbench: () => callCloud('workbench'),
getMembers: (params) => callCloud('members:list', { params }),
createMember: (data) => callCloud('members:create', { data }),
updateMember: (id, data) => callCloud('members:update', { id, data }),
deleteMember: (id) => callCloud('members:delete', { id }),
// ... 共 24 个 action 方法
};
设计亮点:
- 统一错误处理:401 自动清登录态跳回登录页;其他错误直接 toast 后端返回的 error 文案(比如演示账号的 403 提示就是前端自动弹出的);
- 兼容双登录模式:
funcName === 'login'时走 REST 登录接口,业务时走 /api/mp,一个函数封装全部; - 无 ES2020 新语法:为了兼容微信小程序旧版基础库,全程只用 ES6 语法(没有
??、?.); - token 存储在 uni storage:跨页面共享,30 天有效期。
九、后端 /api/mp 统一入口(24 个 action)
后端 web-server/server.js 新增了一个统一入口,模仿云函数的 wx.cloud.callFunction 语义:
// 后端统一入口:POST /api/mp { action, ...业务参数 }
app.post('/api/mp', (req, res) => {
const body = req.body || {};
const action = body.action;
// 鉴权:Authorization Bearer 或 body._token(兼容旧调用)
let token = (req.headers.authorization || '').replace('Bearer ', '');
if (!token && body._token) token = String(body._token);
if (!token) return res.json({ code: 401, error: '未登录或登录已过期' });
let user;
try { user = jwt.verify(token, JWT_SECRET); }
catch (e) { return res.json({ code: 401, error: '未登录或登录已过期' }); }
// 只读演示账号拦截写操作(增/改/删/标记)
if (READONLY_DEMO_USERS.includes(user.username) && WRITE_ACTION_RE.test(action)) {
return res.json({ code: 403, error: '演示账号仅可查看,请注册登录后使用' });
}
const handler = mpHandlers[action];
if (!handler) return res.json({ code: 400, error: '未知操作: ' + action });
try {
const data = handler(user.username, body);
if (data && data.error) return res.json({ code: 400, error: data.error });
res.json({ code: 0, data });
} catch (e) {
res.json({ code: 500, error: '服务器错误: ' + e.message });
}
});
处理器用对象映射组织,共 24 个 action:
const mpHandlers = {
workbench(username) { /* 工作台统计 */ },
'members:list'(username, event) { /* 分页+搜索+筛选 */ },
'members:detail'(username, event) { },
'members:create'(username, event) { },
'members:update'(username, event) { },
'members:delete'(username, event) { },
'members:meetings'(username, event) { /* 某党员的会议记录 */ },
'orgs:list'(username, event) { },
'meetings:list' / 'detail' / 'create' / 'update' / 'delete',
'activities:list' / 'create' / 'delete',
'development:list' / 'create' / 'update' / 'mark',
'study:list' / 'create',
'archives:list' / 'categories' / 'fileurl',
'todos:list' / 'create' / 'update' / 'delete'
};
API_BASE 从云函数改成自建服务器,其余代码全部保留。十、微信一键登录与账号绑定
这是迁移后新增的核心能力。完整链路如下:
10.1 登录流程
用户打开小程序
└─▶ onLoad 自动调 wx.login() 拿临时 code
└─▶ POST /api/wx-login { code }
└─▶ 服务器用 code 向微信官方换 openid
└─▶ SELECT users WHERE openid = ?
├─ 已绑定 ──▶ 签发 JWT(30天),免密进入
└─ 未绑定 ──▶ 返回 { needBind: true, openid }
└─▶ 前端存 pending_openid,提示用户用账号密码登录/注册
└─▶ 登录/注册请求自动带上 openid
└─▶ 服务器写入 users.openid,完成绑定
10.2 关键代码
前端(login.vue):
handleWechatLogin(silent = false) {
if (silent) {
const token = uni.getStorageSync('token');
if (token) { uni.switchTab({ url: '/pages/index/index' }); return; }
}
request.wechatLogin().then((data) => {
if (data && data.token) {
// 已绑定:直接登录
uni.setStorageSync('token', data.token);
uni.setStorageSync('user', data.user);
uni.switchTab({ url: '/pages/index/index' });
} else if (data && data.needBind && data.openid) {
// 未绑定:暂存 openid,提示绑定
uni.setStorageSync('pending_openid', data.openid);
uni.showToast({ title: '该微信未绑定账号,请用已有账号登录绑定', icon: 'none' });
}
});
}
// 登录/注册时自动带上暂存的 openid 完成绑定
const pendingOpenid = uni.getStorageSync('pending_openid');
if (pendingOpenid) {
payload.openid = pendingOpenid;
uni.removeStorageSync('pending_openid');
}
后端(/api/wx-login):
app.post('/api/wx-login', async (req, res) => {
const { code } = req.body || {};
const appid = process.env.WX_APPID;
const secret = process.env.WX_SECRET;
if (!appid || !secret) return res.json({ code: 400, error: '微信登录未配置' });
const url = 'https://api.weixin.qq.com/sns/jscode2session?appid=' + appid
+ '&secret=' + secret + '&js_code=' + encodeURIComponent(code)
+ '&grant_type=authorization_code';
const r = await fetch(url).then((x) => x.json());
const openid = String(r.openid || '');
const user = db.prepare('SELECT * FROM users WHERE openid = ?').get(openid);
if (!user) {
return res.json({ code: 0, data: { needBind: true, openid } });
}
const token = jwt.sign({ username: user.username }, JWT_SECRET, { expiresIn: '30d' });
res.json({ code: 0, data: { token, user: {...} } });
});
ecosystem.config.js 注入环境变量 WX_APPID/WX_SECRET,且 pm2 改环境变量必须 delete + start(restart 不生效)。十一、数据模型与 ID 映射
数据库只有两张表:
users (username, password, display_name, openid, created_at, source)
user_data (username PRIMARY KEY, data JSON)
// data 是一个完整 JSON,每个用户独立空间:
{
owner: 'moran',
orgs: [], // 组织架构
members: [], // 党员
meetings: [], // 三会一课
activities: [], // 主题党日
study: [], // 学习台账
todos: [], // 待办
archives: [], // 档案
development: [] // 发展对象
}
ID 映射是迁移中最容易踩的坑:网页版用数字 id,云端数据是字符串 _id。后端做了兼容处理:
// 返回时附加 _id = String(id),匹配时兼容数字/字符串
function withId(rec) {
return Object.assign({}, rec, { _id: String(rec.id) });
}
function idMatch(a, b) {
return String(a) === String(b);
}
十二、演示账号设计(只读 + 不绑微信)
为了让用户先体验再注册,系统内置了演示账号 moran,并做了三重保护:
| 保护 | 实现 |
|---|---|
| 只读(禁止增改删) | READONLY_DEMO_USERS = ['moran'],写操作一律 403 |
| 演示数据脱敏 | 组织为”机关党总支/第一/第二党支部”,党员为占位”党员01~10″,地点统一”党员活动室”,无任何真实信息 |
| 不绑定微信 | login/wx-login 均拦截演示账号的 openid 绑定 |
// 只读演示账号
const READONLY_DEMO_USERS = ['moran'];
const READONLY_MSG = '演示账号仅可查看,请注册登录后使用';
const WRITE_ACTION_RE = /:(create|update|delete|mark)$/;
// /api/mp 入口拦截
if (READONLY_DEMO_USERS.includes(user.username) && WRITE_ACTION_RE.test(action)) {
return res.json({ code: 403, error: READONLY_MSG });
}
十三、小程序部署教程(手把手)
下面是把小程序部署到微信的完整流程,跟着做就能跑起来。
第 1 步:准备工作
| 需要的东西 | 说明 | 费用 |
|---|---|---|
| 微信小程序账号 | 在 微信公众平台 注册,个人身份证即可 | 注册免费,认证 30 元/年(可选) |
| 微信开发者工具 | 官方下载,选稳定版 | 免费 |
| 源码压缩包 | 文末提供下载 | — |
第 2 步:导入项目到开发者工具
下载源码解压后,打开开发者工具 → 点「导入」→ 选择源码目录 → 填入你的 AppID:
项目名称:智慧党建
目录:<源码解压路径>/src
AppID:你的小程序 AppID
后端服务:微信云开发(或自建服务器)
/Users/you/projects/zhdj。第 3 步:部署后端(云开发 或 自建服务器)
方案 A:微信云开发
开发者工具顶部工具栏 → 点「云开发」按钮 → 开通并创建环境。然后找到 cloudfunctions 目录,逐个右键部署 3 个云函数(login / api / initDB),选择「上传并部署:云端安装依赖」。重点:把 initDB 云函数超时时间改为 60 秒(默认 3 秒导入大数据会失败)。
方案 B:自建服务器(推荐)
按第十四节部署网页版后端后,把 src/utils/request.js 里的 API_BASE 改成你的服务器域名即可,无需部署任何云函数。
第 4 步:初始化数据库
云开发方案,在云开发控制台 → 云函数 → 选中 initDB → 「云端测试」,依次执行:
{"action":"collections"} # 创建 8 个集合
{"action":"init"} # 导入基础数据
{"action":"import:archives","batch":1} # 614 条档案分 7 批导入
{"action":"import:archives","batch":2}
...
{"action":"count:archives"} # 预期 {"count":614}
第 5 步:注册账号并登录
系统没有默认账号(安全设计)。点「没有账号?立即注册」→ 填写用户名、密码(至少 6 位)→ 注册成功后自动登录。每个账号独立数据空间。
第 6 步:发布体验版给同事
- 开发者工具点顶部「上传」→ 填版本号(如 1.0.0)→ 上传成功;
- 登录微信公众平台 → 「管理」→「版本管理」→ 找到刚上传的版本 → 点「选为体验版」;
- 「管理」→「成员管理」→「体验成员」→ 添加对方微信号,对方扫码即可使用(上限 15 人)。
十四、网页版部署:一键脚本 + 服务器
之前写过小程序版的部署教程,需要微信开发者工具、云开发环境,门槛不低。这次做了网页版——一个 HTML 文件 + 一个 Node.js 服务,浏览器打开就能用,不依赖微信、不需要云开发付费。
14.1 一键安装脚本(本地测试 / 单机使用)
跨平台一键脚本(Linux/macOS/Windows 全覆盖),一条命令搞定从零到能用:
Linux / macOS:
bash install.sh
Windows:
powershell -ExecutionPolicy Bypass -File install.ps1
脚本自动做的事:
- 检测 Node.js 18+,没装就自动下载安装(支持 x64 / ARM64)
- npm install 安装后端依赖(Express + better-sqlite3 + JWT)
- 自动生成本地版前端(API 地址自动配置,解决跨域)
- 后台启动后端服务,端口被占用自动切换
- 自动打开浏览器进入登录页
14.2 服务器正式部署(多人使用)
| 步骤 | 命令 | 说明 |
|---|---|---|
| 1. 上传文件 | scp web-server/* 用户@服务器:/opt/zhdj-web-server/ |
后端文件传到服务器 |
| 2. 安装依赖 | cd /opt/zhdj-web-server && npm install |
需要 build-essential + python3 |
| 3. PM2 守护 | pm2 start server.js --name zhdj-web-server |
关掉 SSH 也不停 |
| 4. Nginx 代理 | location /api/ proxy_pass http://127.0.0.1:3100 |
前端页面 + API 反向代理 |
| 5. HTTPS | sudo certbot --nginx -d 你的域名 |
Let’s Encrypt 免费证书 |
部署后访问 https://你的域名/api/ping,返回 {"code":0,"data":"pong"} 即成功。敏感信息(JWT_SECRET / WX_APPID / WX_SECRET)通过环境变量注入,不要硬编码。
十五、网页版 vs 小程序版
| 对比项 | 网页版 | 小程序版 |
|---|---|---|
| 部署成本 | 一台服务器即可,零付费 | 需微信云开发(正式版付费) |
| 访问方式 | 浏览器/微信打开链接 | 微信小程序内打开 |
| 微信登录 | 账号密码注册登录 | 支持微信一键免密登录 |
| 功能 | 完整功能(8 大模块) | 完整功能(8 大模块) |
| 数据隔离 | 每人独立空间,跨设备同步 | 每人独立空间(owner_id) |
| 适用场景 | 不想搞小程序、快速试用 | 正式党建台账管理 |
两套系统共享同一套业务逻辑和数据格式,选择哪个取决于你的需求。小程序版适合需要微信生态(微信登录、小程序分享)的场景;网页版适合不想折腾微信开发者工具、只想快速跑起来的场景。
十六、踩坑记录与注意事项
| 坑 | 原因 | 解决方案 |
|---|---|---|
| 小程序报「未登录」 | 云函数时代的 token 校验逻辑与 REST 不同 | 统一 JWT:Authorization Bearer 或 body._token 双通道兼容 |
| 数据库缺 openid 列 | 旧库结构没有该字段 | 启动时 ALTER TABLE users ADD COLUMN openid 自动补列 |
| pm2 环境变量不生效 | pm2 restart 不会加载新的 env |
必须 pm2 delete + pm2 start 重新启动 |
| ES2020 语法报错 | 微信小程序旧版基础库不支持 ??/?. |
前端产物只用 ES6 语法 |
| id 数字/字符串不匹配 | 网页版数字 id vs 云端字符串 _id | withId() + idMatch() 兼容 |
| 注册绑定微信失败 | register 接口没接收 openid | 补充 openid 参数 + 防重复绑定校验 |
| 演示账号被改数据 | 无只读限制 | READONLY_DEMO_USERS + WRITE_ACTION_RE 拦截 |
| macOS 工具中文路径 bug | 编译器对中文路径支持有缺陷 | 项目放到纯英文路径 |
| 614 条档案导入超时 | 云函数默认超时 3 秒 | 超时调到 60 秒 + 分批导入 |
| 退出后被微信免密”拉回去” | 登录页 onLoad 自动静默登录 | 退出时写入 skip_auto_login 标记 |
| better-sqlite3 安装报错 | 缺编译工具 | Ubuntu: build-essential;CentOS: gcc-c++ make;macOS: xcode-select |
十七、总结
这个项目从内网 SQLite 桌面系统起步,经过「网页版 → 云开发小程序 → 自建服务器双端」三轮迭代。核心收获:
- 复用后端:小程序与网页版共用 Express 后端 + SQLite,一套部署两份前端;
- 复用调用语义:/api/mp 的 action 分发模拟云函数 callFunction,小程序端迁移近乎零成本;
- 复用数据:同一张 user_data 表,双端数据实时同步;
- 复用用户体系:账号密码 + 微信登录双通道,共享 JWT。
最终效果:完全免费(自建服务器 + 无云开发额度限制)、双端数据打通、微信一键登录、演示账号零门槛体验。
十八、源码下载(评论后可见)
本文涉及的所有源码已经整理为开源包,包含完整小程序源码、网页版前端、后端服务、部署脚本,可直接下载使用:
- ✅ 完整脱敏(不含任何真实用户数据、密码、密钥)
- ✅ 含 README 详细部署文档
- ✅ MIT 开源协议,可自由使用、修改、分发
- ✅ 含小程序源码(uni-app)、网页版前端(HTML)、后端服务(Node.js + Express)
💡 为了防止机器爬虫批量下载,源码包采用「评论后可见」机制。
请在下方评论区留下任意一句话(如「感谢分享」「已下载」),刷新页面后即可看到下载链接。
部署遇到问题?欢迎在评论区留言,作者会及时回复。
如果觉得有用,也欢迎 请作者喝杯咖啡 ☕ 支持开源。





暂无评论内容