Express 集成Sequelize+Sqlite3 默认开启WAL 进程间通信 Conf 打包成可执行 exe 文件

article/2025/6/10 5:29:44

代码:express-exe: 将Express开发的js打包成exe服务丢给客户端使用

实现目标

  1. Express 集成 Sequelize 操作 Sqlite3 数据库;

  2. 启动 Sqlite3 时默认开启 WAL 模式,避免读写互锁,支持并发读;

  3. 利用 Conf 实现主进程与 Express 服务的底层通信;

  4. 打包成可执行文件,在 mac 和 windows 两套系统中均可使用;

Express 集成 Sequelize

  1. Express 在启动时,会检查所有可能的 require 类库,并将该类库实例化,作为单例方式向外导出使用

  2. 这样包括一些类库引入时需要前置构建的工作,即可交给启动函数来执行,执行完毕后,再对外抛出

  3. 遇到异步类函数比较难操作,传统的 commonJS 没有顶层 await,实现等待处理比较麻烦

  4. createModel 函数无需 async/await 化,虽然能保证模型 findAll 之前必然完成 wal 和 sync,但实际操作是主进程以子进程方式先启动 http 服务,之后才开始运行主渲染进程 和 webview 进程,两者产生并发性操作几乎为 0,因此无需耗费心思在每次 User 使用前都等待 db 的统一化操作

  5. Model 只是一种映射类 和 db 链接是两个概念,前者存在的意义是实现 sql 语句拼接的函数化以及返回 sql 的对象化填充时有个 Map 对应,sequelize 才是将 Model 转化的 sql 语句拿去执行的人

    1. 因此,我们可以集中管理 sequelize,封装一个 sequelize 收集类专门收集所有的 db 连接,等服务关闭时,统一关闭 db 链接

以下就是上面实现的具体代码,分为 userModel.js,instanceModel.js,sequelizeCollector.js 三个文件

// userModel.js
// src/models/userModel.js
const { DataTypes } = require('sequelize');
const { createModel } = require('./instanceModel');const { Model, sequelize } = createModel('User', {id: {type: DataTypes.INTEGER,autoIncrement: true,primaryKey: true,},name: {type: DataTypes.STRING,allowNull: false,},email: {type: DataTypes.STRING,allowNull: false,unique: true,},
});module.exports = { User: Model, sequelize };// instanceModel.js
// src/models/instanceModel.js
const sequelizeCollector = require("../db/sequelizeCollector");function createModel(modelName, attributes, options = {}) {// Get Sequelize instance from collectorconst sequelize = sequelizeCollector.getInstance(modelName);// 定义模型const Model = sequelize.define(modelName, attributes, {tableName: modelName.toLowerCase(),freezeTableName: true,timestamps: true,...options,});// 同步模型到数据库(创建表)Model.sync({ force: false }).then(() => {console.log(`${modelName} table synchronized`);}).catch((err) => {console.error(`Failed to sync ${modelName} table:`, err);});return { Model, sequelize };
}module.exports = { createModel };// sequelizeCollector.js
const { Sequelize } = require('sequelize');
const path = require('path');
const ConfigManager = require("../config/ConfManager");class SequelizeCollector {constructor() {this.connections = new Map(); // Using Map to store modelName -> sequelize instancethis.configManager = new ConfigManager({ configName: "starter.http", configPath: "./config" });}// 添加 Sequelize 连接addConnection(modelName, sequelizeInstance) {if (sequelizeInstance && typeof sequelizeInstance.close === 'function') {this.connections.set(modelName, sequelizeInstance);console.log(`Sequelize connection added for model: ${modelName}`);}}// 获取或创建 Sequelize 实例getInstance(modelName) {// Check if instance already existsif (this.connections.has(modelName)) {return this.connections.get(modelName);}// Create new Sequelize instance if it doesn't existconst dbPath = path.join(this.configManager.get("dbPath"), '/sqlite', `${modelName.toLowerCase()}.db`);const sequelize = new Sequelize({dialect: 'sqlite',storage: dbPath,logging: false,});// Enable WAL modesequelize.query('PRAGMA journal_mode = WAL;').then(() => {console.log(`WAL mode enabled for database: ${dbPath}`);}).catch((err) => {console.error(`Failed to enable WAL mode for ${dbPath}:`, err);});// Add to connectionsthis.addConnection(modelName, sequelize);return sequelize;}// 移除 Sequelize 连接removeConnection(modelName) {if (this.connections.has(modelName)) {this.connections.delete(modelName);console.log(`Sequelize connection removed for model: ${modelName}`);}}// 关闭所有 Sequelize 连接async closeAllConnections() {const closePromises = Array.from(this.connections.entries()).map(async ([modelName, sequelize]) => {try {await sequelize.close();this.connections.delete(modelName);console.log(`Sequelize connection closed for model: ${modelName}`);} catch (error) {console.error(`Error closing Sequelize connection for ${modelName}:`, error);}});await Promise.all(closePromises);}// 获取当前连接数量getConnectionCount() {return this.connections.size;}
}// 单例模式
const sequelizeCollector = new SequelizeCollector();module.exports = sequelizeCollector;// userService.js
// services/userService.js
const formatUtils = require('../utils/format'); // 引入工具模块
const {User} = require('../models/userModel');// 获取所有用户
async function getUsers() {try {/*** 下面注释的代码是将 createModel 函数 async/await* 这种方式可保证 wal 和 sync 均完成的后再执行 findAll* 但考虑现实情况, wal 和 sync 操作不需要 await* 因为是单步操作 协程调度下 单步操作基本为交替操作* wal 和 sync 距离很近 操作可在 findAll 前完成* 从输出的 console 也能判断该结论* 此外 因为主渲染进程 和 webview 都在 http 启动之后才开始运行* 所以 wal 和 sync 异步操作没有任何影响* 在协程逻辑下 sync 操作不会和 findAll 同时存在* 因此 sync 理论上会先执行后再执行 findAll*/// const {User, sequelize} = await UserPromise;// console.log(User)const users = await User.findAll();return users.map(user => ({...user.toJSON(),name: formatUtils.capitalize(user.name),email: formatUtils.capitalize(user.email),createdAt: formatUtils.formatDate(user.createdAt),}));} catch (error) {throw new Error(`Error fetching users: ${error.message}`);}
}

打成可执行文件

  1. 利用 windows 的 wsl,运行在 centos 系统中

    1. CentOS7 的类库不支持 node18 版本,会报类库错误

升级为 CentOS8 会遇到以下问题

wsl centos8 二进制安装文件

https://github.com/wsldl-pg/CentWSL/releases

除了将基本镜像更换为腾讯云,还要把下面截图的两个文件里面的镜像更换为腾讯云

curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.cloud.tencent.com/repo/centos8_base.repo
CentOS8 需要额外更改两个文件

解决阿里云CentOS8 yum安装appstream报错,更新yum后无法makecache的问题_errors during downloading metadata for repository -CSDN博客

搞定镜像后,要安装开发包,否则报prebuild-install 错误,这样在pkg . 打包时就不会报prebuild-install错误了

dnf groupinstall -y "Development Tools"
dnf install -y python3 python3-devel
yum install -y python39
echo "alias python3=/usr/bin/python3.9" >> ~/.bashrc
source ~/.bashrc

macos 打包直接双击运行问题

  1. 双击运行默认路径是 /User/xxx 用户根目录,必须到指定路径,使用 ./pkg-express 方式运行,才能正确寻找路径

  2. 因此需要使用绝对路径更好

注意

  1. 切换不同平台时,需要运行 npm rebuild,否则是没有这个平台的二进制的,即使都能打包出来对应的可执行文件,但不可运行


http://www.hkcw.cn/article/HMjPZJlqTl.shtml

相关文章

无人机巡检智能边缘计算终端技术方案‌‌——基于EFISH-SCB-RK3588工控机/SAIL-RK3588核心板的国产化替代方案‌

一、方案核心价值‌ ‌实时AI处理‌:6TOPS NPU实现无人机影像的实时缺陷检测(延迟<50ms)‌全国产化‌:芯片、操作系统、算法工具链100%自主可控‌极端环境适配‌:-40℃~85℃稳定运行,IP65防护等…

蓝桥杯国赛训练 day1

目录 k倍区间 舞狮 交换瓶子 k倍区间 取模后算组合数就行 import java.util.HashMap; import java.util.Map; import java.util.Scanner;public class Main {static Scanner sc new Scanner(System.in);public static void main(String[] args) {solve();}public static vo…

Monorepo 详解:现代前端工程的架构革命

以下是一篇关于 Monorepo 技术的详细技术博客,采用 Markdown 格式,适合发布在技术社区或团队知识库中。 🧩 深入理解 Monorepo:现代项目管理的利器 在现代软件开发中,项目规模日益庞大,模块之间的依赖关系…

Vue 树状结构控件

1、效果图如下所示&#xff1a; 2、网络请求的数据结构如下&#xff1a; 3、新建插件文件&#xff1a;menu-tree.vue&#xff0c;插件代码如下&#xff1a; <template><div class"root"><div class"parent" click"onParentClick(pare…

【VLAs篇】01:GROOT N1—面向通用人形机器人的开放基础模型

栏目内容论文标题GROOT N1: 一个面向通用人形机器人的开放基础模型 (GROOT N1: An Open Foundation Model for Generalist Humanoid Robots)作者/机构NVIDIA关键词人形机器人 (Humanoid Robots), 基础模型 (Foundation Model), 视觉-语言-动作模型 (VLA), 双系统架构 (Dual-Sys…

【摘录】显示屏购买要注意的参数

4K显示器是指具备4K分辨率的显示器设备。4K的名称来源于其横向解析度约为4000像素&#xff0c;分辨率有3840x2160和40962160像素2种超高分辨率规格。相比主流的1080P全高清分辨率&#xff0c;4K显示器增加数百万个像素点&#xff0c;画面的精细程度及显示品质有质的飞跃。 将屏…

C++语法系列之特殊类设计

一、请设计一个类&#xff0c;不能被拷贝 其实就是防止拷贝构造和赋值运算符的重载&#xff0c;这个在C11中讲了&#xff0c;在C98之前可以声明为private&#xff0c;现在直接等于delete就可以了 //C98 class A { public:A(){} private:A(const A& a);A& operator(co…

网络安全厂商F5推出AI Gateway,化解大模型应用风险

AI正以前所未见的速度重塑数字化体验。然而&#xff0c;企业在加速落地现代化数字体验的过程中&#xff0c;其在保障和交付AI应用方面仍面临严峻挑战。这些应用需处理海量数据&#xff0c;涉及复杂流量模式&#xff0c;并引入更高级的安全威胁&#xff0c;而企业当前的安全能力…

调用.net DLL让CANoe自动识别串口号

1.前言 CANoe9.0用CAPL控制数控电源_canoe读取程控电源电流值-CSDN博客 之前做CAPL通过串口控制数控电源&#xff0c;存在一个缺点&#xff1a;更换电脑需要改串口号 CSDN上有类似的博客&#xff0c;不过要收费&#xff0c;本文根据VID和PID来自动获取串口号&#xff0c;代码…

C++中锁与原子操作的区别及取舍策略

文章目录 锁与原子操作的基本概念锁&#xff08;Lock&#xff09;原子操作&#xff08;Atomic Operations&#xff09; 锁与原子操作的区别1. **功能**2. **性能**3. **复杂性**4. **适用场景** 锁与原子操作的取舍策略1. **简单变量操作**2. **复杂共享资源**3. **性能敏感场景…

知识拓展卡———————RSTP与MSTP的简要说明

我们在之前的学习过程中了解了STP&#xff08;生成树协议&#xff09;的各个端口角色选举的相关概念&#xff0c;今天我们再来拓展一下关于STP的扩展性知识点MSTP与RSTP。 目录 RSTP&#xff08;Rapid Spanning Tree Protocol,快速生成树协议&#xff09;: 端口角色&#xf…

NSSCTF [LitCTF 2025]test_your_nc

[复现]绕过学的还是太差了&#xff0c;多积累吧 ​​​​​​题目 题目: 给了一个python文件 #!/bin/python3 import osprint("input your command")blacklist [cat,ls, ,cd,echo,<,${IFS},sh,\\]while True:command input()for i in blacklist:if i in com…

(10)Fiddler抓包-Fiddler如何设置捕获Firefox浏览器的Https会话

1.简介 经过上一篇对Fiddler的配置后&#xff0c;绝大多数的Https的会话&#xff0c;我们可以成功捕获抓取到&#xff0c;但是有些版本的Firefox浏览器仍然是捕获不到其的Https会话&#xff0c;需要我们更进一步的配置才能捕获到会话进行抓包。 2.环境 1.环境是Windows 10版…

持续领跑中国异地组网路由器市场,贝锐蒲公英再次登顶销量榜首

作为国产远程连接SaaS服务的创领者&#xff0c;贝锐持续引领行业发展&#xff0c;旗下贝锐蒲公英异地组网路由器&#xff0c;凭借出色的技术实力和市场表现&#xff0c;斩获2024年线上电商平台市场销量份额中国第一的佳绩&#xff0c;充分彰显了其在网络解决方案与异地组网领域…

Redis底层数据结构之深入理解跳表(2)

上一篇文章中我们详细讲述了跳表的增添、查找和修改的操作&#xff0c;这篇文章我们来讲解一下跳表在多线程并发时的安全问题。在Redis中&#xff0c;除了网络IO部分和大文件的后台复制涉及到多线程外&#xff0c;其余任务执行时全部都是单线程&#xff0c;这也就意味着在Redis…

振动力学:有阻尼单自由度系统(简谐力激励的受迫振动)

本文讨论外力作用下的单自由度系统的受迫振动&#xff0c;特别是详细讨论了系统的共振特性。 1. 受迫振动的解及其组成 根据文章1和2的描述&#xff0c;此时简谐力外力 f ( t ) f 0 sin ⁡ ( ω t ) f(t) f_0 \sin(\omega t) f(t)f0​sin(ωt)。因此振动方程为&#xff1a;…

Vert.x学习笔记-EventLoop与Handler的关系

Vert.x学习笔记 一、底层机制&#xff1a;事件驱动的核心引擎二、协作流程&#xff1a;事件分发与执行三、线程安全&#xff1a;EventLoop与Handler的约束四、性能优化&#xff1a;最佳实践与注意事项五、典型场景与架构设计六、总结 在Vert.x中&#xff0c;**EventLoop&#x…

DevEco Studio的使用

IDE环境的搭建 快速开始 因为本版本的DevEco Studio为一体化版本&#xff0c;已经包含了SDK&#xff0c;构建插件&#xff0c;ohpm等工具&#xff0c;所以您 在安装完成后即可开箱即用&#xff0c;进行工程开发&#xff0c;无需配置环境。首先看一下安装DevEco Studio的相关流…

手动删除网页上的禁止复制事件

以Edge浏览器为环境、以网络上一个文档为例。 右击页面&#xff0c;打开【检查】工具。选择元素&#xff0c;打开【事件侦听器】&#xff1a; 展开copy&#xff0c;删除里面的事件&#xff1a; 选中文字&#xff0c;进行复制

【MATLAB去噪算法】基于CEEMD联合小波阈值去噪算法(第三期)

02.去噪算法原理 1.引言 传统EMD方法存在模态混叠问题&#xff0c;即信号成分在不同IMF分量中出现碎片化分布。为改进这一问题&#xff0c;Huang等&#xff08;1999&#xff09;提出间歇性测试算法&#xff0c;但效果有限。Wu和Huang&#xff08;2009&#xff09;发展的集合经…