跳到主要内容
版本:0.17.0+

marknote-electron-prisma-sqlite

  1. 目标
  2. 在生产环境下可以读取.env变量
  3. 目前已经实现在生产环境下,拥有环境变量,prisma 可以读取
  4. 实现方式
  5. 实现细节
  6. 在打包环境下,没有环境变量
  7. 不考虑环境变量,直接硬编码数据库文件地址
  8. electron-builder load .env
  9. 打包环境下,竟然读到了环境变量
  10. 重开项目进行分段测试!
  11. 摊牌了

本篇谨以记录一天一夜(2022 年 1 月 1 日晚 8 点到 1 月 2 日晚 11 点)才解决某软件打包后运行异常的问题,原本只为弄清问题,理清思路,才一步步调试记录,遂成文章。

本次为破解难题,可谓心情沉重与复杂,全程关灯关门听着大提琴与钢琴奋战才有所获。但最终的答案,却并非来自问题本身,而是涉及到问题背后的问题,因而,给我的教训更为深刻,价值也更为宝贵,遂刊之。

目标

  • ✅ G1. 在开发环境下,实现初始化数据库、运行中 CRUD 数据库
  • ✅ G2. 在生产环境下,实现初始化数据库、运行中 CRUD 数据库
  • ⚪ G1. 在打包环境下,实现初始化数据库、运行中 CRUD 数据库(update: ✅ solved since it's out of permission)

在生产环境下可以读取.env变量

目前已经实现在生产环境下,拥有环境变量,prisma 可以读取

实现方式

webpack.config.main.prod.ts中,使用dotenv-webpack插件,会自动读取.env变量 picture 1

实现细节

  1. 运行npm run build
picture 2
  1. 运行electron release/app
picture 4

可以看到,已经有DATABASE_URL变量,该输出由源代码执行:

// src/main/db.ts
import dotenv from 'dotenv';
import {app} from 'electron';
import path from 'path';

dotenv.config();

const dbPath = path.join(app.getPath('userData'), 'sqlite.db');
const dbUrl = `file:${dbPath}?connection_limit=1`;
console.log(`The db url specified is ${dbUrl}, and the envs are:`);
console.log(process.env);
  1. 该输出证明,build环境中已经有了.env变量

在打包环境下,没有环境变量

不考虑环境变量,直接硬编码数据库文件地址

遇到了 Query 的问题,猜测是query_engine或者@prisma没有存放到指定位置的问题。

PrismaClientKnownRequestError: Failed to validate the query: `Field does not exist on enclosing type.` at `Query.findManyerp`
picture 5

electron-builder load .env

打包环境下,竟然读到了环境变量

picture 1

去掉以下之后,依旧有:

// package.json
{
"from": "prisma.env",
"to": ".env"
}
picture 2

再尝试去掉以下,还是有。。

// package.json
"node_modules/.prisma/client/**/*",
picture 3

——————

再尝试去掉最后的一项:

// package.json
'node_modules/@prisma/client/**/*';
picture 4

他喵的,还是有啊。

难道是缓存?

我删掉再试一遍!

——————

picture 5

沃日啊,还有!

这下我就不明白了……

不知道是不是dotenv-webpack还是prisma generate搞的鬼。

我再把dotenv-webpack取消了看看!

——————

picture 6

哈哈哈哈,果然没有了!

看样子真跟这个有关,那现在我再加回来,看看还有没有。

——————

果然又有了!

picture 7

那现在,我再去程序中,把dotenvconfig句,取消,应该就没了吧!

// src/main/db.ts
dotenv.config();

——————

擦,还是有! picture 8

如果果然如此的话,那我直接用默认的options属性,是不是就可以了!

// src/main/db.ts
-export const prisma = new PrismaClient(options);
+export const prisma = new PrismaClient();

这样,预期还是一样,会报query的问题,而不是像之前那样,报环境变量的问题。

因为,在option为空的情况下,prisma会去读prisma/schema.prisma,然后里面默认加载的是env(DATABASE_URL)

—————

然而意外发生了!程序打不开了!只有一片空白!

picture 10

但我用lldb却显示是正常的。当然,lldb太强大了,不能作为程序是否能正常运行的标准。

picture 9

等等,沃日,又出现了!卧槽,连数据库都连接上了!

picture 11

沃日,我好像悟✨✨到了什么!

看!默认的配置,除了url还有一个provider哦!!!

picture 12

所以!!!

如果我们在程序中,直接同时指定这两项,是不是就等价于读取了本地schema.prisma了!

——————

之前用 url 的配置,虽然正确,但是覆盖了prisma的文件配置,然而,要想数据库真正正确地连接,除了url项之外,可还要有其他项的啊!

// src/main/db.ts
@@ -17,6 +17,8 @@ const options: PrismaClientOptions = {
datasources: {
db: {
url: dbUrl,
+ // @ts-ignore
+ provider: 'sqlite',
},
},
};

为啥要加这个@ts-ignore呢?因为d.ts里没有这个定义:

picture 13

所以,是我为了和schema.prisma对上,才加的,不知道结果会咋样……

——————

好的,又进入等待环节了……

picture 14

不过,根据上次经验,可能意味着是成功的。

picture 15

我擦,很快就打脸了啊,并不是的!

这里应该是被类型报错了,意味着我不能加provider关键字。然后按照报错提示,去 read 了 more:

picture 16

You see!这是在 api 上做了限制,只可以'programmatically'修改url,其他的信息是不可以的!

那行吧!

既然我们的环境变量现在是奏效的,那就直接用环境变量吧,这里不加任何参数【TODO:当然了,我们等会要去测一下,到底是哪个prisma文件进行初始化的,以及 url 到底是是啥】

——————

呜呜呜,在大概 20-30 秒的空白后,程序终于启动了,表现良好!

picture 17

但我现在真地好奇,它到底是插的哪个数据库………………

总感觉,可能就是我本地的那个数据库……

如果是这样,我把那个数据库文件换换位置,它是不是又凉凉了…………

可能真的是本地的!因为我发现我的navicat被阻止了!

picture 18

刚刚插完!navicat 又好用了!而且一共 23 万条,是和本次插入目标一致的!

picture 20picture 21

我有一个大胆的想法!

是不是因为,navicat 占着资源不放,导致我的程序启动很慢的!

因为我设置的connection_limit=1,为了防止插入失败!

——————

不行啊,还是很慢!还是 30 秒!我刚刚数了!

picture 22

那是不是因为我在初始化的时候,就启动query,导致阻塞了呢?

我先试试不在启动的时候插入。

PASS: 我记得github上,也有start wait too long的问题的,晚点去看看。

——————

所以我先把 db 的初始化 query 放到了菜单栏里,主程序不在初始时执行任何数据库动作。

// src/main/menu.ts
{
label: 'test database connect',
click() {
prisma.erp
.findMany({ take: 2 })
.then((e) => {
console.log(`queried data: ${e}, it should has 2 items`);
return 1;
})
.catch(console.error);
},
},

这样,预期程序启动速度就会很快了吧!

picture 23

沃日啊,并没有!

还是 30 多秒呢!

行啊,再测一个!

是不是数据库连接就很慢,我直接不初始化数据库行不行?

哦不行,太麻烦了,之前试过,要改很多地方,主要是 typescript 会检查导入,我直接把数据库变量取消的话,需要同时修改很多才能通过编译。

✅ 测试无数据库情况下的启动速度。结果:非常快!

——————

但是,你猜,你知道现在是啥情况吗?

是我用 lldb 或者程序启动,秒开,一切正常;接着我在内部包点击打开,需要 30 秒,然后一切正常;最后我直接在程序 logo 上点击,需要一分多钟,然后数据库没有数据结果。。

奔溃啊。

搞了这么久,还是老问题:

picture 24

使用 asar 依旧不行。

重开项目进行分段测试!

  1. 剔除 db 模块(prisma, sqlite)之后秒开,2022-01-03 21:04:34
  2. 。。。

ONE HOUR LATER...

picture 25

摊牌了

历经整整有 30 个小时,原因终于在一个犄角旮旯的地方找到了:权限!!!

image-20220103231434862

由于我将 sqlite 数据库存在项目里(默认行为),但并有打包进入程序,所以在开发时(无论是开发模式还是生产模式)没有问题,但是打包成可运行程序后,就有读写权限问题了。

这导致程序启动的时候,直接 cpu 飙升(到七八十),为啥呢?肯定是陷入了权限申请的无线循环!

然后三十多秒中甚至一分多钟后才打开,为啥呢?因为超时放弃了!

那为什么在摸索的过程中有出现过可以打开的情况呢?

那是因为我有过将 sqlite 存到 userdata 的尝试,那样连接的时候就没问题了,只不过 prisma 找不到,因为 prisma 要提前配置。

那接下来怎么办?

肯定还是不能在 userdata 里直接存业务数据的把,这样太大了?但也许也不是这么个意思,app 就那么大(1-300M),数据是单独存在程序对应的数据区的,即使把程序卸载,数据还在,这个模式应该还是比较合适的。不合适的是直接把 sqlite(尤其是已经有数据的)打包进程序,虽然我有这么做过,网上也有这么做的,但是场景不同啊,别人打包进去可能只是存一些基本数据、用户数据等,不像我这个场景,是用来存业务的,存业务应该是在 userdata 里新开数据库!这样才稳妥!

诶……

ref:

SPECIAL THANKS:

about electron:

about prisma:

about environment variables: (a good discussion about whether to use dynamic env)